Compartir a través de


Asignación de datos mediante flujos de datos

Importante

En esta página se incluyen instrucciones para administrar componentes de operaciones de IoT de Azure mediante manifiestos de implementación de Kubernetes, que se encuentra en versión preliminar. Esta característica se proporciona con varias limitacionesy no se debe usar para cargas de trabajo de producción.

Consulte Términos de uso complementarios para las versiones preliminares de Microsoft Azure para conocer los términos legales que se aplican a las características de Azure que se encuentran en la versión beta, en versión preliminar o que todavía no se han publicado para que estén disponibles con carácter general.

Use el lenguaje de asignación de flujos de datos para transformar datos en Operaciones de IoT de Azure. La sintaxis es una manera sencilla, pero eficaz, de definir asignaciones que transforman datos de un formato a otro. En este artículo se proporciona información general sobre el lenguaje de asignación de flujos de datos y los conceptos clave.

La asignación le permiten transformar datos de un formato a otro. Tenga en cuenta el registro de entrada siguiente:

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

Compárelo con el registro de salida:

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

En el registro de salida, se realizan los cambios siguientes en los datos del registro de entrada:

  • Campos a los que se ha cambiado de nombre: el campo Birth Date ahora es Date of Birth.
  • Campos reestructurados: tanto Name como Date of Birth se agrupan en la nueva categoría de Employee.
  • Campo eliminado: el campo Place of birth se quita porque no está presente en la salida.
  • Campo agregado: el campo Base Salary es un campo nuevo en la categoría Employment.
  • Valores de campo modificados o combinados: el campo Position de la salida combina los campos Position y Office de la entrada.

Las transformaciones se logran mediante la asignación, que normalmente implica lo siguiente:

  • Definición de entrada: identificación de los campos de los registros de entrada que se usan.
  • Definición de salida: especificación de dónde y cómo se organizan los campos de entrada en los registros de salida.
  • Conversión (opcional): modificación de los campos de entrada que caben en los campos de salida. expression es necesario cuando se combinan varios campos de entrada en un único campo de salida.

A continuación se muestra una asignación de ejemplo:

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

En el ejemplo se asigna:

  • Asignación uno a uno: BirthDate se asigna directamente a Employee.DateOfBirth sin conversión.
  • Asignación de varios a uno: se combina Position y Office en un único campo de Employment.Position. La fórmula de conversión ($1 + ", " + $2) combina estos campos en una cadena con formato.
  • Datos contextuales: BaseSalary se agrega desde un conjunto de datos contextual denominado position.

Referencias de campo

Las referencias de campo muestran cómo especificar rutas de acceso en la entrada y salida mediante la notación de puntos, como Employee.DateOfBirth, o bien el acceso a datos desde un conjunto de datos contextual mediante $context(position).

Propiedades de metadatos MQTT y Kafka

Al usar MQTT o Kafka como origen o destino, puede acceder a las propiedades de varios metadatos en el lenguaje de asignación. Estas propiedades de usuario se pueden asignar en la entrada o salida.

Propiedades de metadatos

  • Tema: funciona tanto para MQTT como para Kafka. Contiene la cadena donde se publicó el mensaje. Ejemplo: $metadata.topic.
  • Propiedad de usuario: en MQTT, hace referencia a los pares clave-valor de forma libre que puede llevar un mensaje MQTT. Por ejemplo, si el mensaje MQTT se publicó con una propiedad de usuario con la clave "priority" y el valor "high", la referencia de $metadata.user_property.priority contiene el valor "high". Las claves de propiedad de usuario pueden ser cadenas arbitrarias y pueden requerir escape: $metadata.user_property."weird key" usa la clave "weird key" (con un espacio).
  • Propiedad del sistema: este término se usa para cada propiedad que no es una propiedad de usuario. Actualmente, solo se admite una sola propiedad del sistema: $metadata.system_property.content_type, que lee la propiedad de tipo de contenido del mensaje MQTT (si se establece).
  • Encabezado: es el equivalente de Kafka de la propiedad de usuario de MQTT. Kafka puede usar cualquier valor binario para una clave, pero el flujo de datos solo admite claves de cadena UTF-8. Ejemplo: $metadata.header.priority. Esta funcionalidad es similar a las propiedades del usuario.

Asignación de propiedades de metadatos

Asignación de datos de entrada

En el ejemplo siguiente, la propiedad topic de MQTT se asigna al campo origin_topic en la salida:

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

Si la propiedad de usuario priority está presente en el mensaje MQTT, en el ejemplo siguiente se muestra cómo asignarla a un campo de salida:

inputs: [
  '$metadata.user_property.priority'
]
output: 'priority'
Asignación de salida

También puede asignar propiedades de metadatos a un encabezado de salida o a una propiedad de usuario. En el ejemplo siguiente, topic del MQTT se asigna al campo origin_topic en la propiedad de usuario de la salida:

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

Si la carga entrante contiene un campo priority, en el ejemplo siguiente se muestra cómo asignarla a una propiedad de usuario MQTT:

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

El mismo ejemplo para Kafka:

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

Selectores de conjuntos de datos de contextualización

Estos selectores permiten que las asignaciones integren datos adicionales de bases de datos externas, denominadas conjuntos de datos de contextualización.

Filtrado de registros

El filtrado de registros implica establecer condiciones para seleccionar qué registros se deben procesar o quitar.

Notación de puntos

La notación de puntos se usa ampliamente en informática para hacer referencia a campos, incluso de manera recursiva. En programación, los nombres de campo normalmente constan de letras y números. Un ejemplo de notación de puntos estándar podría ser como el de este ejemplo:

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

En un flujo de datos, una ruta de acceso descrita por la notación de puntos podría incluir cadenas y algunos caracteres especiales sin necesidad de escape:

inputs: [
  'Person.Date of Birth'
]

En otros casos, es necesario el escape:

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

El ejemplo anterior, entre otros caracteres especiales, contiene puntos dentro del nombre del campo. Si no se escapa, el nombre del campo serviría como separador en la notación de puntos.

Mientras un flujo de datos analiza una ruta de acceso, trata solo dos caracteres como especiales:

  • Los puntos (.) actúan como separadores de campo.
  • Las comillas simples, cuando se colocan al principio o al final de un segmento, inician una sección con escape donde los puntos no se tratan como separadores de campo.

Cualquier otro carácter se trata como parte del nombre del campo. Esta flexibilidad es útil en formatos como JSON, donde los nombres de campo pueden ser cadenas arbitrarias.

En Bicep, todas las cadenas deben estar entre comillas simples ('). Los ejemplos sobre las comillas adecuadas en YAML para Kubernetes no se aplican.

Escape

La función principal del escape en una ruta de acceso con notación de puntos es dar cabida al uso de puntos que forman parte de nombres de campo en lugar de separadores:

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

En este ejemplo, la ruta de acceso consta de tres segmentos: Payload, Tag.10 y Value.

Reglas de escape en la notación de puntos

  • Aplicar escape a cada segmento por separado: si varios segmentos contienen puntos, esos segmentos se deben incluir entre comillas dobles. Otros segmentos también se pueden incluir entre comillas, pero no afecta a la interpretación de la ruta de acceso:

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

  • Uso adecuado de comillas dobles: las comillas dobles deben abrir y cerrar un segmento con escape. Las comillas del centro del segmento se consideran parte del nombre del campo:

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

En este ejemplo se definen dos campos: Payload y He said: "Hello", and waved. Cuando aparece un punto en estas circunstancias, sigue funcionando como separador:

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

En este caso, la ruta de acceso se divide en los segmentos Payload, He said: "No y It is done" (empezando por un espacio).

Algoritmo de segmentación

  • Si el primer carácter de un segmento es una comilla, el analizador busca la siguiente comilla. La cadena incluida entre estas comillas se considera un único segmento.
  • Si el segmento no comienza con una comilla, el analizador identifica los segmentos buscando el siguiente punto o el final de la ruta de acceso.

Wildcard (Carácter comodín)

En muchos escenarios, el registro de salida se parece mucho al registro de entrada, y solo se necesitan pequeñas modificaciones. Cuando se trabaja con registros que contienen numerosos campos, la especificación manual de asignaciones para cada campo puede resultar tediosa. Los caracteres comodín simplifican este proceso al permitir asignaciones generalizadas que se pueden aplicar automáticamente a varios campos.

Imagine un escenario básico para comprender el uso de los asteriscos en las asignaciones:

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

Esta configuración muestra una asignación básica en la que cada campo de la entrada se asigna directamente al mismo campo de la salida sin cambios. El asterisco (*) actúa como un carácter comodín que coincide con cualquier campo del registro de entrada.

Aquí se muestra cómo funciona el asterisco (*) en este contexto:

  • Coincidencia de patrones: el asterisco puede coincidir con uno o varios segmentos de una ruta de acceso. Actúa como marcador de posición para los segmentos de la ruta de acceso.
  • Coincidencia de campos: durante el proceso de asignación, el algoritmo evalúa cada campo del registro de entrada con el patrón especificado en inputs. El asterisco del ejemplo anterior coincide con todas las rutas posibles, lo que ajusta de forma eficaz todos los campos individuales de la entrada.
  • Segmento capturado: la parte de la ruta de acceso con la que coincide el asterisco se conoce como captured segment.
  • Asignación de salida: en la configuración de salida, captured segment se coloca donde aparece el asterisco. Esto significa que la estructura de la entrada se conserva en la salida, y captured segment rellena el marcador de posición proporcionado por el asterisco.

Otro ejemplo muestra cómo se pueden usar caracteres comodín para buscar coincidencias con subsecciones y moverlas de manera conjunta. En este ejemplo se aplanan eficazmente las estructuras anidadas dentro de un objeto JSON.

JSON original:

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

Configuración de asignación que usa caracteres comodín:

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

JSON resultante:

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

Colocación de los caracteres comodín

Al colocar un carácter comodín, debe seguir estas reglas:

  • Un solo asterisco por referencia de datos: solo se permite un asterisco (*) dentro de una única referencia de datos.
  • Coincidencia de segmentos completa: el asterisco debe coincidir siempre con un segmento completo de la ruta de acceso. No se puede usar para que coincida solo con una parte de un segmento, como path1.partial*.path3.
  • Posicionamiento: el asterisco se puede colocar en varias partes de una referencia de datos:
    • Al inicio: *.path2.path3: aquí, el asterisco coincide con cualquier segmento que lleva a path2.path3.
    • En el centro: path1.*.path3: en esta configuración, el asterisco coincide con cualquier segmento entre path1 y path3.
    • Al final: path1.path2.*: el asterisco al final coincide con cualquier segmento que aparezca después de path1.path2.
  • La ruta de acceso que contiene el asterisco debe incluirse entre comillas simples (').

Caracteres comodín de entrada múltiple

JSON original:

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

Configuración de asignación que usa caracteres comodín:

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

JSON resultante:

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

Si hay caracteres comodín de entrada múltiple, el asterisco (*) debe representar de forma coherente el mismo elemento Captured Segment en cada entrada. Por ejemplo, cuando * captura Saturation en el patrón *.Max, el algoritmo de asignación espera que el elemento Saturation.Min correspondiente coincida con el patrón *.Min. En este caso, * se sustituye por el elemento Captured Segment de la primera entrada, lo que guía la coincidencia para las entradas posteriores.

Considere este ejemplo detallado:

JSON original:

{
  "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
    }
  }
}

Configuración inicial de asignación que usa caracteres comodín:

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

Esta asignación inicial intenta crear una matriz (por ejemplo, para Opacity: [0.88, 0.91, 0.89, 0.89]). Se produce un error en esta configuración porque:

  • El primer elemento *.Max de entrada captura un segmento como Saturation.
  • La asignación espera que las entradas posteriores estén presentes en el mismo nivel:
    • Saturation.Max
    • Saturation.Min
    • Saturation.Avg
    • Saturation.Mean

Como Avg y Mean se anidan dentro de Mid, el asterisco de la asignación inicial no captura correctamente estas rutas de acceso.

Configuración de asignación corregida:

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

Esta asignación revisada captura con precisión los campos necesarios. Especifica correctamente las rutas de acceso para incluir el objeto Mid anidado, lo que garantiza que los asteriscos funcionen eficazmente en distintos niveles de la estructura JSON.

Comparación de la segunda regla con la especialización

Al usar el ejemplo anterior de caracteres comodín de entrada múltiple, tenga en cuenta las siguientes asignaciones que generan dos valores derivados para cada propiedad:

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

Esta asignación está pensada para crear dos cálculos independientes (Avg y Diff) para cada propiedad en ColorProperties. En este ejemplo se muestra el resultado:

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

Aquí, la definición de la segunda asignación de las mismas entradas actúa como una segunda regla para la asignación.

Ahora, considere un escenario en el que un campo específico necesita un cálculo diferente:

{
  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'
}

En este caso, el campo Opacity tiene un cálculo único. Hay dos opciones para controlar este escenario superpuesto:

  • Incluya ambas asignaciones para Opacity. Como los campos de salida son diferentes en este ejemplo, no se invalidan entre ellos.
  • Use la regla más específica para Opacity y quite la más genérica.

Considere un caso especial para los mismos campos a fin de decidir la acción correcta:

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

Un campo output vacío en la segunda definición implica no escribir los campos en el registro de salida (lo que quita Opacity). Esta configuración tiene más de Specialization que de Second Rule.

Resolución de asignaciones superpuestas por flujos de datos:

  • La evaluación avanza desde la regla superior en la definición de asignación.
  • Si una nueva asignación se resuelve en los mismos campos que una regla anterior, se aplican las siguientes condiciones:
    • Se calcula un elemento Rank para cada entrada resuelta en función del número de segmentos que captura el carácter comodín. Por ejemplo, si Captured Segments son Properties.Opacity, Rank es 2. Si solo es Opacity, Rank es 1. Una asignación sin caracteres comodín tiene un valor Rank de 0.
    • Si Rank en la última regla es igual o superior a la regla anterior, un flujo de datos lo trata como una instancia de Second Rule.
    • De lo contrario, el flujo de datos trata la configuración como Specialization.

Por ejemplo, la asignación que dirige Opacity.Max y Opacity.Min a una salida vacía tiene un elemento Rank de cero. Como la segunda regla tiene un elemento Rank inferior a la anterior, se considera una especialización e invalida la regla anterior, que calcularía un valor para Opacity.

Caracteres comodín en conjuntos de datos de contextualización

Ahora, veamos cómo se pueden usar los conjuntos de datos de contextualización con caracteres comodín a través de un ejemplo. Considere un conjunto de datos denominado position que contiene el registro siguiente:

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

En un ejemplo anterior, se ha usado un campo específico de este conjunto de datos:

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

Esta asignación copia BaseSalary del conjunto de datos de contexto directamente en la sección Employment del registro de salida. Si quiere automatizar el proceso e incluir todos los campos del conjunto de datos position en la sección Employment, puede usar caracteres comodín:

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

Esta configuración permite una asignación dinámica en la que cada campo del conjunto de datos position se copia en la sección Employment del registro de salida:

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

Último valor conocido

Puede realizar un seguimiento del último valor conocido de una propiedad. Sufijo el campo de entrada con ? $last para capturar el último valor conocido del campo. Cuando falta un valor en una carga de entrada posterior, el último valor conocido se asigna a la carga de salida.

Por ejemplo, considere la siguiente asignación:

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

En este ejemplo, se realiza el seguimiento del último valor conocido de Temperature. Si una carga de entrada posterior no contiene un valor Temperature, el último valor conocido se usa en la salida.