Partager via


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ésormais Date of Birth.
  • Champs restructurés: Name et Date of Birth sont regroupés sous la nouvelle catégorie Employee.
  • 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égorie Employment.
  • Valeurs de champ modifiées ou fusionnées : le champ Position dans la sortie combine les champs Position et Office 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 et Office en un champ Employment.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 le captured 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 entre path1 et path3.
    • À la fin : path1.path2.*. L’astérisque à la fin correspond à n’importe quel segment qui suit après path1.path2.
  • 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 comme Saturation.
  • 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 les Captured Segments sont Properties.Opacity, le Rank est 2. Si ce n’est que Opacity, la valeur Rank est 1. Un mappage sans caractères génériques a un Rank é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 une Second Rule.
    • Sinon, le flux de données traite la configuration comme une Specialization.

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.