使用 jq 去展开嵌套的 JSON

19

我想要将一个嵌套的JSON对象展平,例如{"a":{"b":1}}变成{"a.b":1},以便在solr中处理它。

我有11 TB的JSON文件,其中包含嵌套和带有字段名称中点的情况,这意味着elasticsearch(用点表示)或solr(嵌套但没有_childDocument_符号)不能直接处理这些文件。

其他解决方案是将字段名称中的点替换为下划线,并将其推送到ElasticSearch,但是我对Solr的使用经验更好,因此我更喜欢展平解决方案(除非Solr可以直接处理这些嵌套的JSON??)。

如果处理速度比Solr快得多,我将优先选择Elasticsearch,因为我的优先级是尽可能快地处理(因此我选择了jq而不是在Python中编写脚本)。

请帮帮我。

编辑:

我认为第3个和第4个示例可以解决我的问题:https://lucidworks.com/blog/2014/08/12/indexing-custom-json-data/

我很快就会试试。

6个回答

29
你还可以使用以下的jq命令以这种方式来展开嵌套的JSON对象:
[paths(values) as $path | {"key": $path | join("."), "value": getpath($path)}] | from_entries

它的工作原理是:`leaf_paths` 返回一个数组流,该数组表示给定 JSON 文档中出现“叶子元素”的路径,即没有子元素的元素,如数字、字符串和布尔值。我们将该流传入具有 `key` 和 `value` 属性的对象中,其中 `key` 包含路径数组的元素,通过点连接成字符串,而 `value` 包含该路径上的元素。最后,我们将整个过程放入一个数组中,并对其运行 `from_entries`,将一个包含 `{key, value}` 对象的数组转换为包含这些键值对的对象。

3
当JSON包含数组时,此解决方案无法使用。例如:{"a":{"b":[1]}},会引发错误:jq: error (at <stdin>:1): string (".") and number (0) cannot be added - Steve Amerige
4
很好的答案,尽管这会过滤掉任何求值为“false”,即“false”,“null”等的值。这是因为“leaf_paths”是“paths(scalars)”的简写,而尽管“scalars”选择它们,但“paths”仅返回它们不是false的条目。长话短说,将“leaf_paths”替换为“paths(type!=“object”and type!=“array”)”以包括所有内容。 - hraban
1
修复错误 jq: error (at <stdin>:1): string (".") and number (0) cannot be added[leaf_paths as $path | {"key": [$path[] | tostring] | join("."), "value": getpath($path)}] | from_entries - Abhijit

19

这只是Santiago的jq的一个变体:

. as $in 
| reduce leaf_paths as $path ({};
     . + { ($path | map(tostring) | join(".")): $in | getpath($path) })

它避免了键值构造和销毁的开销。

(如果您有访问jq 1.5以上版本的版本,则可以省略“map(tostring)”部分。)

这两个jq解决方案都有两个重要的要点:

  1. Arrays are also flattened. E.g. given {"a": {"b": [0,1,2]}} as input, the output would be:

    {
      "a.b.0": 0,
      "a.b.1": 1,
      "a.b.2": 2
    }
    
  2. If any of the keys in the original JSON contain periods, then key collisions are possible; such collisions will generally result in the loss of a value. This would happen, for example, with the following input:

    {"a.b":0, "a": {"b": 1}}
    

1
@SteveAmerige - 答案已更新,以便与 jq 1.4 及更高版本兼容。 - peak

6
这里有一个解决方案,它使用了 tostreamselectjoinreducesetpath
  reduce ( tostream | select(length==2) | .[0] |= [join(".")] ) as [$p,$v] (
     {}
     ; setpath($p; $v)
  )

3
我最近写了一个名为 jqg 的脚本,可以将任意复杂的JSON格式化,并使用正则表达式搜索结果;要简单地格式化JSON,您的正则表达式应为 '.',它匹配所有内容。与上面的答案不同,该脚本可以处理嵌套数组、falsenull值,并且可以选择将空数组和对象([] & {})视为叶节点。请注意保留html标签,但不要写解释。
$ jq . test/odd-values.json
{
  "one": {
    "start-string": "foo",
    "null-value": null,
    "integer-number": 101
  },
  "two": [
    {
      "two-a": {
        "non-integer-number": 101.75,
        "number-zero": 0
      },
      "true-boolean": true,
      "two-b": {
        "false-boolean": false
      }
    }
  ],
  "three": {
    "empty-string": "",
    "empty-object": {},
    "empty-array": []
  },
  "end-string": "bar"
}

$ jqg . test/odd-values.json
{
  "one.start-string": "foo",
  "one.null-value": null,
  "one.integer-number": 101,
  "two.0.two-a.non-integer-number": 101.75,
  "two.0.two-a.number-zero": 0,
  "two.0.true-boolean": true,
  "two.0.two-b.false-boolean": false,
  "three.empty-string": "",
  "three.empty-object": {},
  "three.empty-array": [],
  "end-string": "bar"
}

jqg是使用jq 1.6进行测试的。

注意:我是jqg脚本的作者。


1

事实证明,curl -XPOST 'http://localhost:8983/solr/flat/update/json/docs' -d @json_file 就是这样做的:

{
    "a.b":[1],
    "id":"24e3e780-3a9e-4fa7-9159-fc5294e803cd",
    "_version_":1535841499921514496
}

编辑1:solr 6.0.1和bin/solr -e cloud一起使用。集合名称为flat,其余都是默认值(包括data-driven-schema也是默认的)。

编辑2:我使用的最终脚本:find . -name '*.json' -exec curl -XPOST 'http://localhost:8983/solr/collection1/update/json/docs' -d @{} \;

编辑3:也可以使用xargs并使用jq添加id字段进行并行处理:find . -name '*.json' -print0 | xargs -0 -n 1 -P 8 -I {} sh -c "cat {} | jq '. + {id: .a.b}' | curl -XPOST 'http://localhost:8983/solr/collection/update/json/docs' -d @-"其中-P是并行度因子。我使用jq设置了一个id,这样同一文档的多次上传不会在集合中创建重复项(当我搜索-P的最优值时,它会在集合中创建重复项)。


0
如@hraban所提到的,leaf_paths不能按预期工作(此外,它已被弃用)。leaf_paths等同于paths(scalars),它返回任何值的路径,对于这些值,scalars返回一个真值。如果它是标量,则scalars返回其输入值,否则返回null。问题在于nullfalse不是真值,因此它们将从输出中删除。以下代码通过直接检查值的类型来解决这个问题:

. as $in
     | reduce paths(type != "object" and type != "array") as $path ({};
          . + { ($path | map(tostring) | join(".")): $in | getpath($path) })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接