JOLT 转换中数组对象合并与结构优化指南

本教程详细阐述了如何在使用 jolt 进行 json 转换时,解决将多个独立对象合并为一个数组内单一对象的常见问题。通过调整 jolt shift 操作中的数组索引策略,即从动态索引 [] 改为固定索引 [0],可以有效消除冗余的 {} 结构。文章还提供了优化 jolt 规范的技巧,包括将 default 操作整合到 shift 阶段,以构建更高效、更符合预期的 json 输出。

JOLT 数组对象合并与结构优化

在使用 JOLT 进行 JSON 数据转换时,经常会遇到需要将源数据中的多个字段映射到目标 JSON 结构中一个数组内的单一对象。然而,如果处理不当,JOLT 可能会为数组中的每个字段创建独立的子对象,导致输出结构与预期不符。本文将详细介绍如何通过精确控制 JOLT shift 操作中的数组索引来解决这一问题,并提供优化 JOLT 规范的实践建议。

问题场景与输入/输出示例

假设我们有一个扁平的输入 JSON 对象,需要将其中的一部分字段转换为目标 JSON 结构中 data 数组内的一个单一对象,每个字段对应一个带 value 键的嵌套结构。

输入 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"
}

期望输出 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[]..value 这样的路径。这会导致 JOLT 为每个 shift 操作在 data 数组中创建一个新的对象,而不是将所有字段合并到一个对象中。

导致问题的 JOLT Spec 示例:

[
  {
    "operation": "shift",
    "spec": {
      "AssetID": "data[].6.value",
      "AssetNumber": "data[].7.value",
      "AssetMaterial": "data[].8.value",
      "AssetName": "data[].9.value",
      "CustRID": "data[].10.value",
      "SerialNumber": "data[].11.value",
      "Room": "data[].12.value",
      "Datacenter": "data[].13.value",
      "UMountingID": "data[].14.value",
      "CabinetAssetID": "data[].15.value",
      "CabinetName": "data[].16.value"
    }
  },
  {
    "operation": "default",
    "spec": {
      "to": "table1"
    }
  },
  {
    "operation": "default",
    "spec": {
      "fieldsToReturn": [6, 7, 8, 9, 10, 11, 12]
    }
  }
]

上述 Spec 产生的输出 JSON (带有冗余 {}):

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

可以看到,data 数组中包含了多个独立的 {} 对象,每个对象只包含一个键值对,这与我们的期望不符。

解决方案:使用固定数组索引

解决此问题的关键在于,在 shift 操作中,为所有需要合并到同一数组元素的字段指定一个固定的数组索引,而不是使用 [] 动态生成新索引。例如,使用 data[0] 将所有字段都映射到 data 数组的第一个元素中。

优化后的 JOLT Spec:

[
  {
    "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",
      // 将 "to" 字段直接整合到 shift 阶段
      "#table1": "to"
    }
  },
  {
    "operation": "default",
    "spec": {
      "fieldsToReturn": [6, 7, 8, 9, 10, 11, 12]
    }
  },
  {
    "operation": "sort"
  }
]

解释:

  • 通过将所有目标路径中的 data[] 替换为 data[0],JOLT 会将所有匹配的输入字段值都写入 data 数组的第一个元素中。由于它们都指向同一个目标对象,因此会自然地合并为一个对象。
  • "#table1": "to" 是一种 JOLT 的语法糖,它表示将字符串字面量 "table1" 作为值,映射到目标路径 to。这种方式比单独使用一个 default 操作来添加静态值更为简洁和高效。

进一步优化与注意事项

  1. 整合 default 操作: 原始的 default 操作用于添加 "to": "table1" 可以直接整合到 shift 阶段,如上述优化后的 Spec 所示,使用 "#": "" 语法。
  2. sort 操作: sort 操作可以确保输出 JSON 对象的键值对按照字母顺序排列,这在某些场景下有助于保持输出的一致性,但并非强制要求。
  3. fieldsToReturn 的处理: fieldsToReturn 数组的添加仍然需要一个 default 操作,因为它是一个静态数组,不依赖于输入字段的转换。将其放在 shift 之后是合理的。

最终的 JOLT Spec 和输出

结合上述优化,最终推荐的 JOLT Spec 如下:

[
  {
    "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" // 将静态值 "table1" 映射到 "to" 键 } }, { "operation": "default", "spec": { "fieldsToReturn": [6, 7, 8, 9, 10, 11, 12] // 添加静态数组 } }, { "operation": "sort" // 可选:对输出对象进行排序 } ]

使用此 Spec 进行转换,将得到完全符合期望的输出:

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

总结

通过本教程,我们学习了在 JOLT shift 操作中,如何通过使用固定的数组索引(如 data[0])而非动态索引(data[]),来有效地将多个输入字段合并到目标数组中的单一对象。这种方法避免了生成冗余的 {} 结构,使输出 JSON 更符合预期。同时,我们还探讨了将静态值添加操作整合到 shift 阶段,以及使用 sort 操作进行输出优化的技巧,从而编写出更简洁、高效的 JOLT 转换规范。理解 JOLT 中数组索引的工作原理是实现复杂 JSON 结构转换的关键。