JOLT转换:将数组中的独立对象合并为单一对象

JOLT转换:将数组中的独立对象合并为单一对象

在处理JSON数据转换时,JOLT是一个功能强大的工具,尤其适用于Nifi等数据流处理场景。本教程将深入探讨一个常见的JOLT转换需求:如何将一个扁平化的JSON输入结构,转换成一个包含单一复杂对象的数组。具体来说,我们将解决如何避免JOLT默认行为产生的“数组中每个键值对都独立成一个对象”的问题,转而将所有键值对聚合到数组的第一个(也是唯一一个)对象中。

理解问题与目标

假设我们有一个扁平化的输入JSON,其中包含一系列资产相关的属性:

输入 JSON 示例:

{
  "AssetID": "1",
  "AssetNumber": "2",
  "AssetMaterial": "Cisco MDS 9706",
  "RackUnits": "9.0",
  "MaterialType": "Chassis",
  "AssetName": "Cisco-MDS-9706_1",
  "CustRID": "A001",
  "SerialNumber": "OU812",
  "Room": "ROOM5",
  "Datacenter": "DC69",
  "UMountingID": "86",
  "CabinetAssetID": "181",
  "CabinetName": "CAB666"
}

我们希望将其转换成一个更结构化的输出,其中所有资产属性都嵌套在一个名为 data 的数组中,并且 data 数组只包含一个对象,该对象包含了所有转换后的属性。此外,还需要添加一些静态字段。

期望输出 JSON 示例:

{
  "data" : [
    {
        "6" : {
          "value" : "1"
        },
        "7" : {
          "value" : "2"
        },
        "8" : {
          "value" : "Cisco MDS 9706"
        },
        "9" : {
          "value" : "Cisco-MDS-9706_1"
        },
        "10" : {
          "value" : "A001"
        },
        "11" : {
          "value" : "OU812"
        },
        "12" : {
          "value" : "ROOM5"
        },
        "13" : {
          "value" : "DC69"
        },
        "14" : {
          "value" : "86"
        },
        "15" : {
          "value" : "181"
        },
        "16" : {
          "value" : "CAB666"
        }
    }
   ],
  "to" : "table1",
  "fieldsToReturn" : [ 6, 7, 8, 9, 10, 11, 12 ]
}

然而,如果仅仅使用 data[].key.value 这样的路径,JOLT的默认行为会将每个 key.value 对都生成为一个独立的子对象,并将其添加到 data 数组中,导致输出结构与期望不符。

常见错误输出 JSON 示例 (每个键值对一个独立对象):

{
  "data": [
    { "6": { "value": "1" } },
    { "7": { "value": "2" } },
    // ... 更多独立对象 ...
    { "16": { "value": "CAB666" } }
  ],
  "to": "table1",
  "fieldsToReturn": [ 6, 7, 8, 9, 10, 11, 12 ]
}

JOLT shift 操作实现对象聚合

要解决上述问题,关键在于利用JOLT shift 操作的路径表达式,将所有转换后的键值对显式地指向数组的同一个索引位置。最直接的方法是使用 data[0]. 来确保所有数据都汇聚到 data 数组的第一个元素中。

以下是实现期望输出的JOLT规范:

[
  {
    "operation": "shift",
    "spec": {
      "AssetID": "data[0].6.value",
      "AssetNumber": "data[0].7.value",
      "AssetMaterial": "

data[0].8.value", "AssetName": "data[0].9.value", "CustRID": "data[0].10.value", "SerialNumber": "data[0].11.value", "Room": "data[0].12.value", "Datacenter": "data[0].13.value", "UMountingID": "data[0].14.value", "CabinetAssetID": "data[0].15.value", "CabinetName": "data[0].16.value", // 添加静态值到输出根目录 "#table1": "to" } }, { "operation": "default", "spec": { "fieldsToReturn": [6, 7, 8, 9, 10, 11, 12] } }, { "operation": "sort" } ]

规范详解

  1. shift 操作的核心:data[0].

    • "AssetID": "data[0].6.value":这是实现目标的关键。
      • data:指定输出中将包含一个名为 data 的数组。
      • [0]:这是最重要的部分。它指示JOLT将所有匹配到的值都放入 data 数组的 第一个 元素(索引为0)中。如果没有 [0],JOLT会为每个匹配到的输入键创建一个新的数组元素。
      • 6.value:在 data 数组的第一个元素内部,创建一个键为 6 的对象,该对象中再包含一个键为 value 的字段,其值为原始 AssetID 的值。其他的 AssetNumber 等字段也以此类推。
    • "#table1": "to":这是一个将静态值添加到输出根目录的技巧。# 符号表示将字符串字面量作为值,并将其映射到 to 键。这比单独使用 default 操作来添加静态值更高效,因为可以在同一个 shift 阶段完成。
  2. default 操作

    • "operation": "default":此操作用于在输出中添加默认值或缺失的字段。
    • "spec": { "fieldsToReturn": [6, 7, 8, 9, 10, 11, 12] }:在这里,它用于在输出的根目录添加一个名为 fieldsToReturn 的数组,其中包含指定的数字。这是一个独立的默认值,与 data 数组的转换是并行的。
  3. sort 操作 (可选)

    • "operation": "sort":此操作用于对输出JSON中的键进行字母排序。它通常用于确保输出的一致性,但对于功能实现本身并非必需。

优化与注意事项

  • 合并静态值到 shift 阶段: 如上述规范所示,将 "#table1": "to" 放在 shift 操作中,可以避免额外的 default 操作,使JOLT规范更简洁高效。
  • 理解 [] 与 [0] 的区别:
    • data[]:JOLT会为每个匹配到的输入路径创建一个新的数组元素。例如,如果输入有10个 AssetID,data[] 会生成一个包含10个元素的数组。
    • data[0]:JOLT会将所有匹配到的输入路径的值都聚合到 data 数组的第一个(索引为0)元素中。
  • JOLT路径表达式的灵活性: JOLT允许使用通配符 (*) 和捕获组 ($1, $2 等) 来处理更复杂的动态结构。本例中,由于输入键是固定的,直接映射是最清晰的方法。
  • 调试JOLT规范: 在开发JOLT规范时,建议使用在线JOLT转换器或Nifi中的JoltTransformJSON处理器进行逐步测试,观察每一步操作的输出,以便更好地理解和调试。

总结

通过本教程,我们学习了如何利用JOLT shift 操作中固定数组索引 data[0] 的强大功能,将扁平化JSON中的多个键值对有效地聚合到一个单一的数组对象中。这种技术在处理需要将多个属性合并为一个复杂实体的数据转换场景中非常实用。掌握 shift 操作的细微差别,特别是数组索引的使用,是编写高效且准确JOLT规范的关键。