Mapper des données à l’aide de flux de données
Important
Cette page contient des instructions pour gérer les composants Opérations Azure IoT en utilisant des manifestes de déploiement Kubernetes, actuellement en préversion. Cette fonctionnalité est fournie avec plusieurs limitations et ne doit pas être utilisée pour les charges de travail en production.
Pour connaître les conditions juridiques qui s’appliquent aux fonctionnalités Azure en version bêta, en préversion ou plus généralement non encore en disponibilité générale, consultez l’Avenant aux conditions d’utilisation des préversions de Microsoft Azure.
Utilisez le langage de mappage de flux de données pour transformer des données dans Opérations Azure IoT. La syntaxe est un moyen simple, mais puissant, de définir des mappages qui transforment des données d’un format vers un autre. Cet article fournit une vue d’ensemble du langage de mappage de flux de données et des concepts clés.
Le mappage vous permet de transformer des données d’un format vers un autre. Considérez l’enregistrement d’entrée suivant :
{
"Name": "Grace Owens",
"Place of birth": "London, TX",
"Birth Date": "19840202",
"Start Date": "20180812",
"Position": "Analyst",
"Office": "Kent, WA"
}
Comparez-le à l’enregistrement de sortie :
{
"Employee": {
"Name": "Grace Owens",
"Date of Birth": "19840202"
},
"Employment": {
"Start Date": "20180812",
"Position": "Analyst, Kent, WA",
"Base Salary": 78000
}
}
Dans l’enregistrement de sortie, les modifications suivantes sont apportées aux données d’enregistrement d’entrée :
- Champs renommés: le champ
Birth Date
est désormaisDate of Birth
. - Champs restructurés:
Name
etDate of Birth
sont regroupés sous la nouvelle catégorieEmployee
. - Champ supprimé : le champ
Place of birth
est supprimé, car il n’est pas présent dans la sortie. - Champ ajouté: le champ
Base Salary
est un nouveau champ dans la catégorieEmployment
. - Valeurs de champ modifiées ou fusionnées : le champ
Position
dans la sortie combine les champsPosition
etOffice
de l’entrée.
Les transformations sont obtenues via un mappage, ce qui implique généralement ce qui suit :
- Définition de l’entrée : identification des champs dans les enregistrements d’entrée qui sont utilisés.
- Définition de la sortie : spécification de l’emplacement et de la façon dont les champs d’entrée sont organisés dans les enregistrements de sortie.
- Conversion (facultative) : modification des champs d’entrée afin qu’ils s’adaptent aux champs de sortie.
expression
est nécessaire lorsque plusieurs champs d’entrée sont combinés en un champ de sortie unique.
Voici un exemple de mappage :
{
inputs: [
'BirthDate'
]
output: 'Employee.DateOfBirth'
}
{
inputs: [
'Position' // - - - - $1
'Office' // - - - - $2
]
output: 'Employment.Position'
expression: '$1 + ", " + $2'
}
{
inputs: [
'$context(position).BaseSalary'
]
output: 'Employment.BaseSalary'
}
L’exemple effectue les mappages suivants :
- Mappage un-à-un :
BirthDate
est directement mappé àEmployee.DateOfBirth
sans conversion. - Mappage plusieurs-à-un : combine
Position
etOffice
en un champEmployment.Position
unique. La formule de conversion ($1 + ", " + $2
) fusionne ces champs en une chaîne mise en forme. - Données contextuelles :
BaseSalary
est ajouté à partir d’un jeu de données contextuel nomméposition
.
Références de champs
Les références de champs montrent comment spécifier des chemins d’accès dans l’entrée et la sortie, à l’aide d’une notation pointée comme Employee.DateOfBirth
, ou accéder à des données d’un jeu de données contextuel par le biais de $context(position)
.
Propriétés des métadonnées MQTT et Kafka
Lorsque vous utilisez MQTT ou Kafka comme source ou destination, vous pouvez accéder à différentes propriétés des métadonnées dans le langage de mappage. Ces propriétés peuvent être mappées dans les entrées ou les sorties.
Propriétés des métadonnées
- Rubrique : fonctionne à la fois pour MQTT et Kafka. Elle contient la chaîne dans laquelle le message a été publié. Exemple :
$metadata.topic
. - Propriété utilisateur : dans MQTT, cela fait référence aux paires clé/valeur de forme libre qu’un message MQTT peut porter. Par exemple, si le message MQTT a été publié avec une propriété utilisateur avec la clé « priority » et la valeur « high », la référence
$metadata.user_property.priority
contient la valeur « high ». Les clés des propriétés utilisateur peuvent être des chaînes arbitraires et peuvent nécessiter d’être échappées :$metadata.user_property."weird key"
utilise la clé « weird key » (avec un espace). - Propriété système : ce terme est utilisé pour chaque propriété qui n’est pas une propriété utilisateur. Actuellement, une seule propriété système est prise en charge :
$metadata.system_property.content_type
, qui lit la propriété de type de contenu du message MQTT (si définie). - En-tête : il s’agit de l’équivalent Kafka de la propriété utilisateur MQTT. Kafka peut utiliser n’importe quelle valeur binaire pour une clé, mais le flux de données prend uniquement en charge les clés de chaîne UTF-8. Exemple :
$metadata.header.priority
. Cette fonctionnalité est similaire aux propriétés utilisateur.
Mappage des propriétés des métadonnées
Mappage d’entrées
Dans l’exemple suivant, la propriété topic
MQTT est mappée au champ origin_topic
dans la sortie :
inputs: [
'$metadata.topic'
]
output: 'origin_topic'
Si la propriété utilisateur priority
est présente dans le message MQTT, l’exemple suivant montre comment la mapper à un champ de sortie :
inputs: [
'$metadata.user_property.priority'
]
output: 'priority'
Sortie mappage
Vous pouvez également mapper les propriétés de métadonnées à un en-tête de sortie ou à une propriété utilisateur. Dans l’exemple suivant, le topic
MQTT est mappé au champ origin_topic
dans la propriété utilisateur de la sortie :
inputs: [
'$metadata.topic'
]
output: '$metadata.user_property.origin_topic'
Si la charge utile entrante contient un champ priority
, l’exemple suivant montre comment le mapper à une propriété utilisateur MQTT :
inputs: [
'priority'
]
output: '$metadata.user_property.priority'
Le même exemple pour Kafka :
inputs: [
'priority'
]
output: '$metadata.header.priority'
Sélecteurs de jeux de données de contextualisation
Ces sélecteurs permettent aux mappages d’intégrer des données supplémentaires de bases de données externes, appelées jeux de données de contextualisation.
Filtrage des enregistrements
Le filtrage des enregistrements implique de définir des conditions pour sélectionner les enregistrements qui doivent être traités ou supprimés.
Notation par points
La notation pointée est largement utilisée dans les sciences informatiques pour référencer des champs, même de manière récursive. En programmation, les noms des champs se composent généralement de lettres et de chiffres. Un échantillon de notation pointée standard peut ressembler à cet exemple :
inputs: [
'Person.Address.Street.Number'
]
Dans un flux de données, un chemin décrit à l’aide de la notation pointée peut inclure des chaînes et certains caractères spéciaux sans avoir besoin d’échappement :
inputs: [
'Person.Date of Birth'
]
Dans d’autres cas, l’échappement est nécessaire :
inputs: [
'Person."Tag.10".Value'
]
L’exemple précédent, parmi d’autres caractères spéciaux, contient des points dans le nom du champ. Sans échappement, le nom du champ servirait de séparateur dans la notation par points elle-même.
Lorsqu’un flux de données analyse un chemin d’accès, il traite uniquement deux caractères comme spéciaux :
- Les points (
.
) agissent en tant que séparateurs de champs. - Les guillemets simples, lorsqu’ils sont placés au début ou à la fin d’un segment, démarrent une section d’échappement où les points ne sont pas traités comme des séparateurs de champs.
Tous les autres caractères sont traités dans le cadre du nom du champ. Cette flexibilité est utile dans les formats tels que JSON, où les noms des champs peuvent être des chaînes arbitraires.
Dans Bicep, toutes les chaînes de caractères sont placées entre guillemets simples ('
). Les exemples de guillemets appropriés dans YAML pour une utilisation de Kubernetes ne s’appliquent pas.
Échappement
La principale fonction de l’échappement dans un chemin d’accès à notation pointée consiste à prendre en charge l’utilisation de points qui font partie des noms de champs plutôt que de constituer des séparateurs :
inputs: [
'Payload."Tag.10".Value'
]
Dans cet exemple, le chemin d’accès se compose de trois segments : Payload
, Tag.10
et Value
.
Règles d’échappement en notation pointée
Échappement de chaque segment séparément : si plusieurs segments contiennent des points, ces segments doivent être placés entre guillemets doubles. D’autres segments peuvent également être placés entre guillemets, mais cela n’affecte pas l’interprétation du chemin d’accès :
inputs: [ 'Payload."Tag.10".Measurements."Vibration.$12".Value' ]
Utilisation appropriée de guillemets doubles : les guillemets doubles doivent ouvrir et fermer un segment d’échappement. Les guillemets au milieu du segment sont considérés comme faisant partie du nom du champ :
inputs: [ 'Payload.He said: "Hello", and waved' ]
Cet exemple définit deux champs : Payload
et He said: "Hello", and waved
. Lorsqu’un point apparaît dans ces circonstances, il continue de servir de séparateur :
inputs: [
'Payload.He said: "No. It is done"'
]
Dans ce cas, le chemin d’accès est divisé en segments Payload
, He said: "No
et It is done"
(commençant par un espace).
Algorithme de segmentation
- Si le premier caractère d’un segment est un guillemet, l’analyseur recherche le guillemet suivant. La chaîne placée entre ces guillemets est considérée comme un segment unique.
- Si le segment ne commence pas par un guillemet, l’analyseur identifie les segments en recherchant le point suivant ou la fin du chemin d’accès.
Caractère générique
Dans de nombreux scénarios, l’enregistrement de sortie ressemble étroitement à l’enregistrement d’entrée, avec uniquement des modifications mineures requises. Lorsque vous traitez des enregistrements qui contiennent de nombreux champs, la spécification manuelle des mappages pour chaque champ peut devenir fastidieuse. Les caractères génériques simplifient ce processus en autorisant des mappages généralisés qui peuvent s’appliquer automatiquement à plusieurs champs.
Prenons un scénario de base pour mieux comprendre l’utilisation des astérisques dans les mappages :
inputs: [
'*'
]
output: '*'
Cette configuration montre un mappage de base où chaque champ de l’entrée est directement mappé au même champ dans la sortie sans aucune modification. L’astérisque (*
) sert de caractère générique qui correspond à n’importe quel champ de l’enregistrement d’entrée.
Voici comment fonctionne l’astérisque (*
) dans ce contexte :
- Critères spéciaux : l’astérisque peut correspondre à un ou plusieurs segments d’un chemin d’accès. Il sert d’espace réservé pour tous les segments du chemin d’accès.
- Mise en correspondance de champ : pendant le processus de mappage, l’algorithme évalue chaque champ dans l’enregistrement d’entrée par rapport au modèle spécifié dans les
inputs
. L’astérisque dans l’exemple précédent établit une correspondance avec tous les chemins possibles, autrement dit, dans les faits, une correspondance avec chaque champ de l’entrée. - Segment capturé : la partie du chemin d’accès auquel correspond l’astérisque est appelée
captured segment
. - Mappage de sortie : dans la configuration de sortie, le
captured segment
est placé là où l’astérisque apparaît. Cela signifie que la structure de l’entrée est conservée dans la sortie, avec lecaptured segment
remplissant l’espace réservé fourni par l’astérisque.
Un autre exemple illustre comment les caractères génériques peuvent être utilisés pour mettre en correspondance des sous-sections et les déplacer ensemble. Cet exemple aplatit les structures imbriquées au sein d’un objet JSON.
JSON d’origine :
{
"ColorProperties": {
"Hue": "blue",
"Saturation": "90%",
"Brightness": "50%",
"Opacity": "0.8"
},
"TextureProperties": {
"type": "fabric",
"SurfaceFeel": "soft",
"SurfaceAppearance": "matte",
"Pattern": "knitted"
}
}
Configuration de mappage qui utilise des caractères génériques :
{
inputs: [
'ColorProperties.*'
]
output: '*'
}
{
inputs: [
'TextureProperties.*'
]
output: '*'
}
JSON résultant :
{
"Hue": "blue",
"Saturation": "90%",
"Brightness": "50%",
"Opacity": "0.8",
"type": "fabric",
"SurfaceFeel": "soft",
"SurfaceAppearance": "matte",
"Pattern": "knitted"
}
Positionnement des caractères génériques
Lorsque vous placez un caractère générique, vous devez suivre les règles suivantes :
- Astérisque unique par référence de données : un seul astérisque (
*
) est autorisé dans une référence de données. - Correspondance de segment complet : l’astérisque doit toujours correspondre à un segment entier du chemin d’accès. Il ne peut pas être utilisé pour établir une correspondance uniquement à une partie d’un segment, tel que
path1.partial*.path3
. - Positionnement : l’astérisque peut être positionné dans différentes parties d’une référence de données :
- Au début :
*.path2.path3
. Ici, l’astérisque correspond à n’importe quel segment qui mène jusqu’àpath2.path3
. - Au milieu :
path1.*.path3
. Dans cette configuration, l’astérisque correspond à n’importe quel segment entrepath1
etpath3
. - À la fin :
path1.path2.*
. L’astérisque à la fin correspond à n’importe quel segment qui suit aprèspath1.path2
.
- Au début :
- Le chemin d’accès contenant l’astérisque doit être placé entre guillemets simples (
'
).
Caractères génériques à plusieurs entrées
JSON d’origine :
{
"Saturation": {
"Max": 0.42,
"Min": 0.67,
},
"Brightness": {
"Max": 0.78,
"Min": 0.93,
},
"Opacity": {
"Max": 0.88,
"Min": 0.91,
}
}
Configuration de mappage qui utilise des caractères génériques :
inputs: [
'*.Max' // - $1
'*.Min' // - $2
]
output: 'ColorProperties.*'
expression: '($1 + $2) / 2'
JSON résultant :
{
"ColorProperties" : {
"Saturation": 0.54,
"Brightness": 0.85,
"Opacity": 0.89
}
}
Si vous utilisez des caractères génériques à plusieurs entrées, l’astérisque (*
) doit représenter de façon cohérente le même Captured Segment
dans chaque entrée. Par exemple, lorsque *
capture Saturation
dans le modèle *.Max
, l’algorithme de mappage s’attend à ce que le Saturation.Min
correspondant corresponde au modèle *.Min
. Ici, *
est remplacé par le Captured Segment
de la première entrée, guidant la mise en correspondance pour les entrées suivantes.
Considérez cet exemple détaillé :
JSON d’origine :
{
"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
}
}
}
Configuration de mappage initiale qui utilise des caractères génériques :
inputs: [
'*.Max' // - $1
'*.Min' // - $2
'*.Avg' // - $3
'*.Mean' // - $4
]
Ce mappage initial tente de générer un tableau (par exemple, pour Opacity
: [0.88, 0.91, 0.89, 0.89]
). Cette configuration échoue car :
- La première entrée
*.Max
capture un segment commeSaturation
. - Le mappage s’attend à ce que les entrées suivantes soient présentes au même niveau :
Saturation.Max
Saturation.Min
Saturation.Avg
Saturation.Mean
Étant donné que Avg
et Mean
sont imbriqués dans Mid
, l’astérisque dans le mappage initial ne capture pas correctement ces chemins.
Configuration du mappage corrigée :
inputs: [
'*.Max' // - $1
'*.Min' // - $2
'*.Mid.Avg' // - $3
'*.Mid.Mean' // - $4
]
Ce mappage révisé capture avec précision les champs nécessaires. Il spécifie correctement les chemins d’accès de façon à inclure l’objet imbriqué Mid
, ce qui garantit que les astérisques fonctionnent efficacement à différents niveaux de la structure JSON.
Deuxième règle et spécialisation
Lorsque vous utilisez l’exemple précédent utilisant des caractères génériques à plusieurs entrées, et considérons les mappages suivants qui génèrent deux valeurs dérivées pour chaque propriété :
{
inputs: [
'*.Max' // - $1
'*.Min' // - $2
]
output: 'ColorProperties.*.Avg'
expression: '($1 + $2) / 2'
}
{
inputs: [
'*.Max' // - $1
'*.Min' // - $2
]
output: 'ColorProperties.*.Diff'
expression: '$1 - $2'
}
Ce mappage a pour but de créer deux calculs distincts (Avg
et Diff
) pour chaque propriété sous ColorProperties
. Cet exemple montre le résultat :
{
"ColorProperties": {
"Saturation": {
"Avg": 0.54,
"Diff": 0.25
},
"Brightness": {
"Avg": 0.85,
"Diff": 0.15
},
"Opacity": {
"Avg": 0.89,
"Diff": 0.03
}
}
}
Ici, la deuxième définition de mappage sur les mêmes entrées agit comme une deuxième règle pour le mappage.
À présent, envisagez un scénario dans lequel un champ spécifique a besoin d’un calcul différent :
{
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'
}
Dans ce cas, le champ Opacity
a un calcul unique. Il existe deux options pour gérer ce scénario de chevauchement :
- Inclure les deux mappages pour
Opacity
. Étant donné que les champs de sortie sont différents dans cet exemple, ils ne se substitueraient pas les uns aux autres. - Utiliser la règle la plus spécifique pour
Opacity
et supprimer la plus générique.
Considérez un cas spécial pour les mêmes champs afin de vous aider à décider de l’action appropriée :
{
inputs: [
'*.Max' // - $1
'*.Min' // - $2
]
output: 'ColorProperties.*'
expression: '($1 + $2) / 2'
}
{
inputs: [
'Opacity.Max' // - $1
'Opacity.Min' // - $2
]
output: ''
}
Un champ output
vide dans la deuxième définition implique de ne pas écrire les champs dans l’enregistrement de sortie (ce qui revient à supprimer Opacity
). Cette configuration correspond davantage à une Specialization
qu’à une Second Rule
.
Résolution des mappages qui se chevauchent par les flux de données :
- L’évaluation progresse à partir de la règle supérieure dans la définition de mappage.
- Si un nouveau mappage résout les mêmes champs qu’une règle précédente, les conditions suivantes s’appliquent :
- Un
Rank
est calculé pour chaque entrée résolue en fonction du nombre de segments capturés par le caractère générique. Par exemple, si lesCaptured Segments
sontProperties.Opacity
, leRank
est 2. Si ce n’est queOpacity
, la valeurRank
est 1. Un mappage sans caractères génériques a unRank
égal à 0. - Si le
Rank
de la dernière règle est supérieur ou égal à celui de la règle précédente, un flux de données le traite comme uneSecond Rule
. - Sinon, le flux de données traite la configuration comme une
Specialization
.
- Un
Par exemple, le mappage qui dirige Opacity.Max
et Opacity.Min
vers une sortie vide a un Rank
égal à 0. La deuxième règle ayant un Rank
inférieur à la précédente, elle est considérée comme une spécialisation et remplace la règle précédente, ce qui calculerait une valeur pour Opacity
.
Caractères génériques dans les jeux de données de contextualisation
À présent, voyons comment les jeux de données de contextualisation peuvent être utilisés avec des caractères génériques grâce à un exemple. Considérez un jeu de données nommé position
qui contient l’enregistrement suivant :
{
"Position": "Analyst",
"BaseSalary": 70000,
"WorkingHours": "Regular"
}
Dans un exemple précédent, nous avons utilisé un champ spécifique de ce jeu de données :
inputs: [
'$context(position).BaseSalary'
]
output: 'Employment.BaseSalary'
Ce mappage copie le BaseSalary
du jeu de données de contexte directement dans la section Employment
de l’enregistrement de sortie. Si vous souhaitez automatiser le processus et inclure tous les champs du jeu de données position
dans la section Employment
, vous pouvez utiliser des caractères génériques :
inputs: [
'$context(position).*'
]
output: 'Employment.*'
Cette configuration autorise un mappage dynamique où chaque champ du jeu de données position
est copié dans la section Employment
de l’enregistrement de sortie :
{
"Employment": {
"Position": "Analyst",
"BaseSalary": 70000,
"WorkingHours": "Regular"
}
}
Dernière valeur connue
Vous pouvez suivre la dernière valeur connue d’une propriété. Ajouter un suffixe ? $last
au champ d’entrée pour capturer la dernière valeur connue du champ. Lorsqu’une propriété manque d’une valeur dans une charge utile d’entrée ultérieure, la dernière valeur connue est mappée à la charge utile de sortie.
Prenons par exemple le mappage suivant :
inputs: [
'Temperature ? $last'
]
output: 'Thermostat.Temperature'
Dans cet exemple, la dernière valeur connue de Temperature
est suivie. Si une charge utile d’entrée ultérieure ne contient pas de valeur Temperature
, la dernière valeur connue est utilisée dans la sortie.