你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

使用数据流映射数据

重要

本页包含使用 Kubernetes 部署清单(目前为预览版)管理 Azure IoT 操作组件的说明。 此功能存在若干限制,不应该用于生产工作负载。

有关 beta 版本、预览版或尚未正式发布的版本的 Azure 功能所适用的法律条款,请参阅 Microsoft Azure 预览版的补充使用条款

使用数据流映射语言在 Azure IoT 操作中转换数据。 语法是一种简单而强大的定义映射的方式,用于将数据从一种格式转换为另一种格式。 本文概述了数据流映射语言和关键概念。

映射允许将数据从一种格式转换为另一种格式。 请考虑以下输入记录:

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

将其与输出记录进行比较:

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

在输出记录中,对输入记录数据进行了以下更改:

  • 已重命名的字段Birth Date 字段现在为 Date of Birth
  • 已重新构造的字段NameDate of Birth 均已分组到新的 Employee 类别下。
  • 已删除的字段:已移除 Place of birth 字段,因为它未显示在输出中
  • 已添加的字段:Base Salary 字段是类别中的 Employment 新字段。
  • 已更改或合并的字段值:输出中的 Position 字段会合并输入中的 PositionOffice 字段。

转换是通过映射实现的,通常涉及以下几个方面

  • 输入定义:标识所使用的输入记录中的字段。
  • 输出定义:指定在输出记录中放置输入字段的位置和方式。
  • 转换(可选):修改输入字段以适应输出字段。 在将多个输入字段合并为单个输出字段时,必须使用 expression

以下映射是一个示例:

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

映射示例:

  • 一对一映射:BirthDate 会直接映射到 Employee.DateOfBirth,不进行转换。
  • 多对一映射:将 PositionOffice 合并为一个 Employment.Position 字段。 转换公式 ($1 + ", " + $2) 将这些字段合并为设置了格式的字符串。
  • 上下文数据:BaseSalary 添加自名为 position 的上下文数据集。

字段参考

字段参考演示如何通过使用点表示法(例如 Employee.DateOfBirth)或通过 $context(position) 访问上下文数据集中的数据来指定输入和输出中的路径。

MQTT 和 Kafka 元数据属性

使用 MQTT 或 Kafka 作为源或目标时,可以使用映射语言访问各种元数据属性。 可以在输入或输出中映射这些属性。

元数据属性

  • 主题:适用于 MQTT 和 Kafka。 其中包含发布消息的字符串。 示例:$metadata.topic
  • 用户属性:在 MQTT 中,这指的是 MQTT 消息可以承载的自由格式键/值对。 例如,如果使用键为“priority”、值为“high”的用户属性发布 MQTT 消息,则 $metadata.user_property.priority 引用将保留值“high”。 用户属性键可以是任意字符串,而且可能需要转义:$metadata.user_property."weird key" 使用键“weird key”(带空格)。
  • 系统属性:此术语用于每个非用户属性。 目前,仅支持一个系统属性:$metadata.system_property.content_type,该属性读取 MQTT 消息的内容类型属性(若已设置)。
  • 标头:这是 MQTT 用户属性的 Kafka 等效项。 Kafka 可以对键使用任何二进制值,但数据流仅支持 UTF-8 字符串键。 示例:$metadata.header.priority。 此功能类似于用户属性。

定映射元数据属性

输入映射

在以下示例中,MQTT topic 属性映射到输出中的 origin_topic 字段:

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

如果 MQTT 消息中存在用户属性 priority,则以下示例演示如何将其映射到输出字段:

inputs: [
  '$metadata.user_property.priority'
]
output: 'priority'
输出映射

也可以将元数据属性映射到输出标头或用户属性。 在以下示例中,MQTT topic 映射到输出的用户属性中的 origin_topic 字段:

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

如果传入有效负载包含 priority 字段,则以下示例演示如何将其映射到 MQTT 用户属性:

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

Kafka 的相同示例:

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

上下文化数据集选择器

这些选择器允许映射集成外部数据库中的额外数据,即称为上下文化数据集。

记录筛选

记录筛选涉及设置条件以选择要处理或删除的记录。

点表示法

点表示法在计算机科学中广泛使用,以引用字段,甚至是进行递归引用。 在编程中,字段名称通常由字母和数字组成。 标准点表示法示例可能如以下示例所示:

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

在数据流中,点表示法描述的路径可能包括字符串和某些无需转义的特殊字符:

inputs: [
  'Person.Date of Birth'
]

在其他情况下,需要转义:

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

上一个示例以及其他特殊字符包含字段名称中的点。 如果不进行转义,则会将字段名称用作点表示法本身中的分隔符。

虽然数据流将分析路径,但只会将两个字符视为特殊:

  • 点 (.) 充当字段分隔符。
  • 单引号位于段的开头或末尾时会开始一个转义部分,其中点不被视为字段分隔符。

任何其他字符都将视为字段名的一部分。 这种灵活性在 JSON 等格式中非常有用,其中字段名可以是任意字符串。

在 Bicep 中,所有字符串都括在单引号 (') 中。 适用于 Kubernetes 的 YAML 中的对应引号的示例不适用。

转义

在点表示法路径中转义的主要功能是支持点作为字段名的一部分而不是作为分隔符使用:

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

在此示例中,路径由三段组成:PayloadTag.10Value

点表示法的转义规则

  • 单独转义每个段:如果有多个段包含点,则必须将这些段括在双引号中。 其他段也可以用引号括起来,但这不会影响路径解释:

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

  • 正确使用双引号:双引号必须打开并关闭转义段。 段中间的任何引号都会被视为字段名称的一部分:

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

此示例定义了两个字段:PayloadHe said: "Hello", and waved。 在以下情况下出现点时,它将继续充当分隔符:

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

在这种情况下,路径将拆分为段 PayloadHe said: "NoIt is done"(以空格开头)。

分段算法

  • 如果段的第一个字符是引号,分析程序将搜索下一个引号。 这些引号之间括起来的字符串被视为单个段。
  • 如果段不以引号开头,分析程序会通过搜索路径的下一个点或末尾来标识段。

通配符

在许多情况下,输出记录与输入记录非常相似,只需进行少量修改。 处理包含多个字段的记录时,手动指定每个字段的映射可能会很繁琐。 通配符支持可自动应用于多个字段的通用映射,简化了此过程。

让我们考虑一个基本场景来了解星号在映射中的使用:

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

此配置显示了一个基本映射,即,输入中的每个字段都直接映射到输出中的同一字段,无需任何更改。 星号 (*) 用作通配符,可匹配输入记录中的任何字段。

下面是星号 (*) 在此上下文中的工作方式:

  • 模式匹配:星号可以匹配路径的单个段或多个段。 它会充当路径中任意段的占位符。
  • 字段匹配:在映射过程中,算法会根据 inputs 中指定的模式评估输入记录中的每个字段。 上一示例中的星号匹配所有可能的路径,从而有效地适应输入中的每个单独字段。
  • 捕获的段:星号匹配的路径部分称为 captured segment
  • 输出映射:在输出配置中,captured segment 放置在星号出现的位置。 这意味着输入的结构保留在输出中,并且 captured segment 会填充星号提供的占位符。

另一个示例演示了如何使用通配符来匹配子节并将它们一起移动。 此示例有效地平展了 JSON 对象中的嵌套结构。

原始 JSON:

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

使用通配符的映射配置:

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

生成的 JSON:

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

通配符放置

放置通配符时,必须遵循以下规则:

  • 每个数据引用一个星号:单个数据引用中只允许使用一个星号 (*)。
  • 完整段匹配:星号必须始终匹配路径的整个段。 它不能用于仅匹配段的一部分,例如 path1.partial*.path3
  • 位置:星号可以放置在数据引用的各个部分:
    • 开头:*.path2.path3 - 此处的星号与 path2.path3 之前的任何段匹配。
    • 中间:path1.*.path3 - 在此配置中,星号与介于 path1path3 之间的任何段匹配。
    • 末尾:path1.path2.* - 末尾的星号与 path1.path2 后面的任何段匹配。
  • 必须使用单引号 (') 将包含星号的路径括起来。

多输入通配符

原始 JSON:

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

使用通配符的映射配置:

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

生成的 JSON:

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

如果使用多输入通配符,星号 (*) 必须在每个输入中始终地表示相同的 Captured Segment。 例如,当 * 捕获模式 *.Max 中的 Saturation 时,映射算法会预期对应的 Saturation.Min 与模式 *.Min 相匹配。 此处,* 由第一个输入中的 Captured Segment 替换,指导后续输入的匹配。

请考虑以下详细示例:

原始 JSON:

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

使用通配符的初始映射配置:

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

此初始映射尝试生成数组(例如对于 Opacity[0.88, 0.91, 0.89, 0.89])。 此配置失败了,因为:

  • 第一个输入 *.Max 会捕获像 Saturation 这样的段。
  • 映射预计后续输入将处于同一级别:
    • Saturation.Max
    • Saturation.Min
    • Saturation.Avg
    • Saturation.Mean

由于 AvgMean 嵌套在 Mid 内,初始映射中的星号无法正确捕获这些路径。

更正的映射配置:

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

此修订后的映射会准确捕获必要的字段。 它正确地指定了包含嵌套 Mid 对象的路径,确保星号在 JSON 结构的不同级别上有效发挥作用。

第二条规则与专用化

使用前面的多输入通配符示例时,请考虑以下映射,它为每个属性生成两个派生值:

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

此映射旨在为 ColorProperties 下的每个属性创建两个单独的计算(AvgDiff)。 此示例显示了结果:

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

在这里,相同输入上的第二个映射定义充当映射的第二个规则。

现在,请考虑特定字段需要不同计算的情形:

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

在这种情况下,Opacity 字段具有唯一的计算。 以下是处理此重叠情形的两个选项:

  • 包括 Opacity 的两个映射。 由于此示例中的输出字段不同,因此它们不会相互替代。
  • Opacity 使用更具体的规则并删除一般规则。

考虑一下相同字段的特殊情况,以帮助确定正确的操作:

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

第二个定义中的空 output 字段意味着不在输出记录中写入字段(有效移除 Opacity)。 此设置比较像 Specialization,而不是 Second Rule

通过数据流解决重叠映射:

  • 评估从映射定义中的最高规则开始。
  • 如果新映射解析为与上一个规则相同的字段,则适用以下条件:
    • 根据通配符捕获的段数,为每个解析的输入计算 Rank。 例如,如果 Captured SegmentsProperties.Opacity,则 Rank 为 2。 如果仅为 Opacity,则 Rank 为 1。 没有通配符的映射的 Rank 为 0。
    • 如果后一个规则的 Rank 等于或高于上一个规则,则数据流会将它视为 Second Rule
    • 否则,数据流会将配置视为 Specialization

例如,将 Opacity.MaxOpacity.Min 定向到空输出的映射的 Rank 为 0。 由于第二个规则的 Rank 低于上一个规则,因此它被视为专用化并会替代上一个规则,该规则将计算 Opacity 的值。

上下文化数据集中的通配符

现在,让我们通过示例了解如何将上下文化数据集与通配符一起使用。 请考虑名为 position 包含以下记录的数据集:

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

在前面的示例中,我们使用了此数据集中的特定字段:

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

此映射将上下文数据集中的 BaseSalary 直接复制到输出记录的 Employment 部分。 如果要自动执行该过程,并将 position 数据集中的所有字段包含在 Employment 部分中,则可以使用通配符:

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

此配置允许动态映射,其中 position 数据集中的每个字段都会复制到输出记录的 Employment 部分中:

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

上一个已知值

可以跟踪属性的最后一个已知值。 将输入字段的后缀设为 ? $last,以捕获该字段的最后一个已知值。 当属性在后续输入有效负载中缺少值时,最后一个已知值将会映射到输出有效负载。

例如,考虑以下映射:

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

在此示例中,将会跟踪 Temperature 的最后一个已知值。 如果后续输入有效负载不包含 Temperature 值,则会在输出中使用最后一个已知值。