Condividi tramite


Eseguire il mapping dei dati usando flussi di dati

Importante

Questa pagina include istruzioni per la gestione dei componenti di Operazioni IoT di Azure usando i manifesti di distribuzione kubernetes, disponibile in anteprima. Questa funzionalità viene fornita con diverse limitazioni e non deve essere usata per i carichi di lavoro di produzione.

Vedere le condizioni per l'utilizzo supplementari per le anteprime di Microsoft Azure per termini legali aggiuntivi che si applicano a funzionalità di Azure in versione beta, in anteprima o in altro modo non ancora disponibili a livello generale.

Usare il linguaggio di mapping dei flussi di dati per trasformare i dati nelle operazioni IoT di Azure. La sintassi è un modo semplice, ma potente, per definire i mapping che trasformano i dati da un formato a un altro. Questo articolo offre una panoramica del linguaggio di mapping dei flussi di dati e dei concetti chiave.

Il mapping consente di trasformare i dati da un formato all'altro. Si consideri il record di input seguente:

{
  "Name": "Grace Owens",
  "Place of birth": "London, TX",
  "Birth Date": "19840202",
  "Start Date": "20180812",
  "Position": "Analyst",
  "Office": "Kent, WA"
}

Confrontarlo con il record di output:

{
  "Employee": {
    "Name": "Grace Owens",
    "Date of Birth": "19840202"
  },
  "Employment": {
    "Start Date": "20180812",
    "Position": "Analyst, Kent, WA",
    "Base Salary": 78000
  }
}

Nel record di output vengono apportate le modifiche seguenti ai dati del record di input:

  • Campi rinominati: il Birth Date campo è ora Date of Birth.
  • Campi ristrutturati: sia Name che Date of Birth sono raggruppati nella nuova Employee categoria.
  • Campo eliminato: il Place of birth campo viene rimosso perché non è presente nell'output.
  • Campo aggiunto: il Base Salary campo è un nuovo campo nella Employment categoria.
  • Valori dei campi modificati o uniti: il Position campo nell'output combina i Position campi e Office dall'input.

Le trasformazioni vengono ottenute tramite mapping, che in genere comporta:

  • Definizione di input: identificazione dei campi nei record di input usati.
  • Definizione di output: specificazione di dove e come sono organizzati i campi di input nei record di output.
  • Conversione (facoltativo): modifica dei campi di input in modo che si adattino ai campi di output. expression è obbligatorio quando più campi di input vengono combinati in un singolo campo di output.

Il mapping seguente è un esempio:

{
  inputs: [
    'BirthDate'
  ]
  output: 'Employee.DateOfBirth'
}
{
  inputs: [
    'Position'  // - - - - $1
    'Office'    // - - - - $2
  ]
  output: 'Employment.Position'
  expression: '$1 + ", " + $2'
}
{
  inputs: [
    '$context(position).BaseSalary'
  ]
  output: 'Employment.BaseSalary'
}

L'esempio esegue il mapping:

  • Mapping uno-a-uno: BirthDate è mappato direttamente a Employee.DateOfBirth senza conversione.
  • Mapping molti-a-uno: combina Position e Office in un singolo campo Employment.Position. La formula di conversione ($1 + ", " + $2) unisce questi campi in una stringa formattata.
  • Dati contestuali: BaseSalary viene aggiunto da un set di dati contestuale denominato position.

Riferimenti ai campi

I riferimenti ai campi mostrano come specificare i percorsi nell'input e nell'output usando la notazione del punto come Employee.DateOfBirth o l'accesso ai dati da un set di dati contestuale tramite $context(position).

Proprietà dei metadati MQTT e Kafka

Quando si usa MQTT o Kafka come origine o destinazione, è possibile accedere a varie proprietà dei metadati nel linguaggio di mapping. Queste proprietà possono essere mappate nell'input o nell'output.

Proprietà dei metadati

  • Argomento: funziona sia per MQTT che per Kafka. Contiene la stringa in cui è stato pubblicato il messaggio. Esempio: $metadata.topic.
  • Proprietà utente: in MQTT, si riferisce alle coppie chiave/valore in formato libero che un messaggio MQTT può contenere. Ad esempio, se il messaggio MQTT è stato pubblicato con una proprietà utente con la chiave "priority" e il valore "high", il $metadata.user_property.priority riferimento contiene il valore "high". Le chiavi delle proprietà utente possono essere stringhe arbitrarie e possono richiedere l'escape: $metadata.user_property."weird key" usa la chiave "chiave strana" (con uno spazio).
  • Proprietà di sistema: questo termine viene usato per ogni proprietà che non è una proprietà utente. Attualmente è supportata solo una singola proprietà di sistema: $metadata.system_property.content_type, che legge la proprietà del tipo di contenuto del messaggio MQTT (se impostata).
  • Intestazione: equivale a Kafka della proprietà utente MQTT. Kafka può usare qualsiasi valore binario per una chiave, ma il flusso di dati supporta solo chiavi stringa UTF-8. Esempio: $metadata.header.priority. Questa funzionalità è simile alle proprietà utente.

Mapping delle proprietà dei metadati

Mapping dell'input

Nell'esempio seguente viene eseguito il mapping della proprietà MQTT topic al origin_topic campo nell'output:

inputs: [
  '$metadata.topic'
]
output: 'origin_topic'

Se la proprietà priority utente è presente nel messaggio MQTT, l'esempio seguente illustra come eseguirne il mapping a un campo di output:

inputs: [
  '$metadata.user_property.priority'
]
output: 'priority'
Mapping degli output

È anche possibile eseguire il mapping delle proprietà dei metadati a un'intestazione di output o a una proprietà utente. Nell'esempio seguente viene eseguito il mapping di MQTT topic al origin_topic campo nella proprietà utente dell'output:

inputs: [
  '$metadata.topic'
]
output: '$metadata.user_property.origin_topic'

Se il payload in ingresso contiene un priority campo, l'esempio seguente illustra come eseguirne il mapping a una proprietà utente MQTT:

inputs: [
  'priority'
]
output: '$metadata.user_property.priority'

Lo stesso esempio per Kafka:

inputs: [
  'priority'
]
output: '$metadata.header.priority'

Selettori del set di dati di contestualizzazione

Questi selettori consentono ai mapping di integrare dati aggiuntivi da database esterni, detti set di dati di contestualizzazione.

Filtraggio dei record

Il filtraggio dei record comporta l'impostazione di condizioni per selezionare i record da elaborare o eliminare.

Notazione con il punto

La notazione dei punti è ampiamente usata in informatica per fare riferimento a campi, anche in modo ricorsivo. Nella programmazione, i nomi dei campi in genere sono costituiti da lettere e numeri. Un esempio standard di notazione con punti potrebbe essere simile a questo esempio:

inputs: [
  'Person.Address.Street.Number'
]

In un flusso di dati, un percorso descritto dalla notazione punto può includere stringhe e alcuni caratteri speciali senza dover eseguire l'escape:

inputs: [
  'Person.Date of Birth'
]

In altri casi, l'escape è necessario:

inputs: [
  'Person."Tag.10".Value'
]

L'esempio precedente, tra gli altri caratteri speciali, contiene punti all'interno del nome del campo. Senza escape, il nome del campo fungerebbe da separatore nella notazione del punto stesso.

Mentre un flusso di dati analizza un percorso, tratta solo due caratteri come speciali:

  • I punti (.) fungono da separatori di campo.
  • Le virgolette singole, se posizionate all'inizio o alla fine di un segmento, iniziano una sezione di escape in cui i punti non vengono considerati come separatori di campo.

Tutti gli altri caratteri vengono considerati come parte del nome del campo. Questa flessibilità è utile in formati come JSON, in cui i nomi dei campi possono essere stringhe arbitrarie.

In Bicep tutte le stringhe sono racchiuse tra virgolette singole ('). Gli esempi relativi all'uso corretto delle virgolette in YAML per Kubernetes non si applicano.

Escape

La funzione primaria di escape in un percorso con notazione con punti consiste nell'usare punti che fanno parte dei nomi di campo anziché separatori:

inputs: [
  'Payload."Tag.10".Value'
]

In questo esempio il percorso è costituito da tre segmenti: Payload, Tag.10e Value.

Regole di escape nella notazione con punti

  • Escape di ogni segmento separatamente: se più segmenti contengono punti, questi segmenti devono essere racchiusi tra virgolette doppie. Anche altri segmenti possono essere racchiusi tra virgolette, ma questo non influisce sull'interpretazione del percorso:

    inputs: [
      'Payload."Tag.10".Measurements."Vibration.$12".Value'
    ]
    

  • Uso corretto delle virgolette doppie: le virgolette doppie devono essere aperte e chiudere un segmento di escape. Le virgolette al centro del segmento sono considerate parte del nome del campo:

    inputs: [
      'Payload.He said: "Hello", and waved'
    ]
    

In questo esempio vengono definiti due campi: Payload e He said: "Hello", and waved. Quando un punto viene visualizzato in queste circostanze, continua a fungere da separatore:

inputs: [
  'Payload.He said: "No. It is done"'
]

In questo caso, il percorso viene suddiviso nei segmenti Payload, He said: "Noe It is done" (a partire da uno spazio).

Algoritmo di segmentazione

  • Se il primo carattere di un segmento è una virgoletta, il parser cerca la virgoletta successiva. La stringa racchiusa tra queste virgolette è considerata un singolo segmento.
  • Se il segmento non inizia con virgolette, il parser identifica i segmenti cercando il punto successivo o la fine del percorso.

Wildcard (Carattere jolly)

In molti scenari, il record di output è simile al record di input, con solo modifiche minime necessarie. Quando si gestiscono record che contengono numerosi campi, la specifica manuale dei mapping per ogni campo può diventare noiosa. I caratteri jolly semplificano questo processo consentendo mapping generalizzati che possono essere applicati automaticamente a più campi.

Si consideri uno scenario di base per comprendere l'uso degli asterischi nei mapping:

inputs: [
  '*'
]
output: '*'

Questa configurazione mostra un mapping di base in cui ogni campo dell'input viene mappato direttamente allo stesso campo nell'output senza alcuna modifica. L'asterisco (*) funge da carattere jolly che corrisponde a qualsiasi campo nel record di input.

Ecco come funziona l'asterisco (*) in questo contesto:

  • Criteri di ricerca: l'asterisco può corrispondere a un singolo segmento o a più segmenti di un percorso. Funge da segnaposto per tutti i segmenti nel percorso.
  • Corrispondenza dei campi: durante il processo di mapping, l'algoritmo valuta ogni campo nel record di input rispetto al modello specificato in inputs. L'asterisco nell'esempio precedente corrisponde a tutti i percorsi possibili, adattando efficacemente ogni singolo campo nell'input.
  • Segmento acquisito: la parte del percorso corrispondente all'asterisco viene definita captured segment.
  • Mapping di output: nella configurazione di output, viene posizionato dove captured segment viene visualizzato l'asterisco. Ciò significa che la struttura dell'input viene mantenuta nell'output, con il captured segment riempimento del segnaposto fornito dall'asterisco.

Un altro esempio illustra come usare i caratteri jolly per trovare le corrispondenze con le sottosezioni e spostarle insieme. In questo esempio vengono rese flat in modo efficace le strutture annidate all'interno di un oggetto JSON.

JSON originale:

{
  "ColorProperties": {
    "Hue": "blue",
    "Saturation": "90%",
    "Brightness": "50%",
    "Opacity": "0.8"
  },
  "TextureProperties": {
    "type": "fabric",
    "SurfaceFeel": "soft",
    "SurfaceAppearance": "matte",
    "Pattern": "knitted"
  }
}

Configurazione del mapping che usa caratteri jolly:

{
  inputs: [
    'ColorProperties.*'
  ]
  output: '*'
}
{
  inputs: [
    'TextureProperties.*'
  ]
  output: '*'
}

JSON risultante:

{
  "Hue": "blue",
  "Saturation": "90%",
  "Brightness": "50%",
  "Opacity": "0.8",
  "type": "fabric",
  "SurfaceFeel": "soft",
  "SurfaceAppearance": "matte",
  "Pattern": "knitted"
}

Posizionamento con caratteri jolly

Quando si inserisce un carattere jolly, è necessario seguire queste regole:

  • Singolo asterisco per riferimento ai dati: all'interno di un singolo riferimento dati è consentito un solo asterisco (*).
  • Corrispondenza segmento completo: l'asterisco deve corrispondere sempre a un intero segmento del percorso. Non può essere usato per trovare la corrispondenza solo con una parte di un segmento, ad esempio path1.partial*.path3.
  • Posizionamento: l'asterisco può essere posizionato in varie parti di un riferimento ai dati:
    • All'inizio: - *.path2.path3 Qui l'asterisco corrisponde a qualsiasi segmento che conduce a path2.path3.
    • Al centro: - path1.*.path3 In questa configurazione l'asterisco corrisponde a qualsiasi segmento tra path1 e path3.
    • Alla fine: - path1.path2.* L'asterisco alla fine corrisponde a qualsiasi segmento che segue dopo path1.path2.
  • Il percorso contenente l'asterisco deve essere racchiuso tra virgolette singole (').

Caratteri jolly multi-input

JSON originale:

{
  "Saturation": {
    "Max": 0.42,
    "Min": 0.67,
  },
  "Brightness": {
    "Max": 0.78,
    "Min": 0.93,
  },
  "Opacity": {
    "Max": 0.88,
    "Min": 0.91,
  }
}

Configurazione del mapping che usa caratteri jolly:

inputs: [
  '*.Max'  // - $1
  '*.Min'  // - $2
]
output: 'ColorProperties.*'
expression: '($1 + $2) / 2'

JSON risultante:

{
  "ColorProperties" : {
    "Saturation": 0.54,
    "Brightness": 0.85,
    "Opacity": 0.89 
  }    
}

Se si usano caratteri jolly multi-input, l'asterisco (*) deve rappresentare in modo coerente lo stesso Captured Segment in ogni input. Ad esempio, quando * acquisisce Saturation nel modello *.Max, l'algoritmo di mapping prevede che il Saturation.Min corrispondente corrisponda al criterio *.Min. In questo caso, * viene sostituito dal Captured Segment dal primo input, guidando la corrispondenza per gli input successivi.

Si consideri questo esempio dettagliato:

JSON originale:

{
  "Saturation": {
    "Max": 0.42,
    "Min": 0.67,
    "Mid": {
      "Avg": 0.51,
      "Mean": 0.56
    }
  },
  "Brightness": {
    "Max": 0.78,
    "Min": 0.93,
    "Mid": {
      "Avg": 0.81,
      "Mean": 0.82
    }
  },
  "Opacity": {
    "Max": 0.88,
    "Min": 0.91,
    "Mid": {
      "Avg": 0.89,
      "Mean": 0.89
    }
  }
}

Configurazione di mapping iniziale che usa caratteri jolly:

inputs: [
  '*.Max'    // - $1
  '*.Min'    // - $2
  '*.Avg'    // - $3
  '*.Mean'   // - $4
]

Questo mapping iniziale tenta di compilare una matrice , ad esempio per Opacity: [0.88, 0.91, 0.89, 0.89]. Questa configurazione ha esito negativo perché:

  • Il primo input *.Max acquisisce un segmento come Saturation.
  • Il mapping prevede che gli input successivi siano presenti allo stesso livello:
    • Saturation.Max
    • Saturation.Min
    • Saturation.Avg
    • Saturation.Mean

Poiché Avg e Mean sono annidati all'interno Middi , l'asterisco nel mapping iniziale non acquisisce correttamente questi percorsi.

Configurazione del mapping corretta:

inputs: [
  '*.Max'        // - $1
  '*.Min'        // - $2
  '*.Mid.Avg'    // - $3
  '*.Mid.Mean'   // - $4
]

Questo mapping modificato acquisisce in modo accurato i campi necessari. Specifica correttamente i percorsi da includere nell'oggetto annidato Mid , che garantisce che gli asterischi funzionino in modo efficace in diversi livelli della struttura JSON.

Seconda regola e specializzazione

Quando si usa l'esempio precedente da caratteri jolly multi-input, considerare i mapping seguenti che generano due valori derivati per ogni proprietà:

{
  inputs: [
    '*.Max'   // - $1
    '*.Min'   // - $2
  ]
  output: 'ColorProperties.*.Avg'
  expression: '($1 + $2) / 2'
}
{
  inputs: [
    '*.Max'   // - $1
    '*.Min'   // - $2
  ]
  output: 'ColorProperties.*.Diff'
  expression: '$1 - $2'
}

Questo mapping è progettato per creare due calcoli separati (Avg e Diff) per ogni proprietà in ColorProperties. Questo esempio mostra il risultato:

{
  "ColorProperties": {
    "Saturation": {
      "Avg": 0.54,
      "Diff": 0.25
    },
    "Brightness": {
      "Avg": 0.85,
      "Diff": 0.15
    },
    "Opacity": {
      "Avg": 0.89,
      "Diff": 0.03
    }
  }
}

In questo caso, la seconda definizione di mapping sugli stessi input funge da seconda regola per il mapping.

Si consideri ora uno scenario in cui un campo specifico richiede un calcolo diverso:

{
  inputs: [
    '*.Max'   // - $1
    '*.Min'   // - $2
  ]
  output: 'ColorProperties.*'
  expression: '($1 + $2) / 2'
}
{
  inputs: [
    'Opacity.Max'   // - $1
    'Opacity.Min'   // - $2
  ]
  output: 'ColorProperties.OpacityAdjusted'
  expression: '($1 + $2 + 1.32) / 2'
}

In questo caso, il campo Opacity ha un calcolo univoco. Due opzioni per gestire questo scenario sovrapposto sono:

  • Includere entrambi i mapping per Opacity. Poiché i campi di output sono diversi in questo esempio, non eseguirebbero l'override tra loro.
  • Usare la regola più specifica per Opacity e rimuovere quella più generica.

Si consideri un caso speciale per gli stessi campi per decidere l'azione corretta:

{
  inputs: [
    '*.Max'   // - $1
    '*.Min'   // - $2
  ]
  output: 'ColorProperties.*'
  expression: '($1 + $2) / 2'
}
{
  inputs: [
    'Opacity.Max'   // - $1
    'Opacity.Min'   // - $2
  ]
  output: ''
}

Un campo output vuoto nella seconda definizione implica la mancata scrittura dei campi nel record di output (rimuovendo in modo efficace Opacity). Questa configurazione è più una Specialization che una Second Rule.

Risoluzione dei mapping sovrapposti in base ai flussi di dati:

  • La valutazione procede dalla regola principale nella definizione del mapping.
  • Se un nuovo mapping viene risolto negli stessi campi di una regola precedente, si applicano le condizioni seguenti:
    • Un Rank viene calcolato per ogni input risolto in base al numero di segmenti acquisiti dai caratteri jolly. Ad esempio, se il Captured Segments è Properties.Opacity, il Rank è 2. Se è solo Opacity, è Rank 1. Un mapping senza caratteri jolly ha un Rank pari a 0.
    • Se il Rank della seconda regola è uguale o superiore alla regola precedente, un flusso di dati lo considera come una Second Rule.
    • In caso contrario, il flusso di dati considera la configurazione come .Specialization

Ad esempio, il mapping diretto Opacity.Max e Opacity.Min a un output vuoto ha un Rank valore pari a 0. Poiché la seconda regola ha un valore inferiore Rank a quello precedente, viene considerata una specializzazione ed esegue l'override della regola precedente, che calcola un valore per Opacity.

Caratteri jolly nei set di dati di contestualizzazione

Si vedrà ora come usare i set di dati di contestualizzazione con caratteri jolly tramite un esempio. Si consideri un set di dati denominato position che contiene il record seguente:

{
  "Position": "Analyst",
  "BaseSalary": 70000,
  "WorkingHours": "Regular"
}

In un esempio precedente è stato usato un campo specifico di questo set di dati:

inputs: [
  '$context(position).BaseSalary'
]
output: 'Employment.BaseSalary'

Questo mapping copia BaseSalary dal set di dati di contesto direttamente nella Employment sezione del record di output. Se si vuole automatizzare il processo e includere tutti i campi del position set di dati nella Employment sezione , è possibile usare i caratteri jolly:

inputs: [
  '$context(position).*'
]
output: 'Employment.*'

Questa configurazione consente un mapping dinamico in cui ogni campo all'interno del set di dati position viene copiato nella sezione Employment del record di output:

{
    "Employment": {      
      "Position": "Analyst",
      "BaseSalary": 70000,
      "WorkingHours": "Regular"
    }
}

Ultimo valore noto

È possibile tenere traccia dell'ultimo valore noto di una proprietà. Suffisso il campo di input con ? $last per acquisire l'ultimo valore noto del campo. Quando una proprietà manca un valore in un payload di input successivo, viene eseguito il mapping dell'ultimo valore noto al payload di output.

Si consideri ad esempio il mapping seguente:

inputs: [
  'Temperature ? $last'
]
output: 'Thermostat.Temperature'

In questo esempio viene rilevato l'ultimo valore noto di Temperature . Se un payload di input successivo non contiene un Temperature valore, nell'output viene usato l'ultimo valore noto.