使用jq将嵌套的JSON文件分解为具有唯一键的平面列表

时间:2017-07-21 03:48:45

标签: json key grouping jq flatten

给定格式为的JSON文件:

{
    "key00" : {
        "key10" : {
            "20170405" : {
                "val0" : 10,
                ...
                "valn" : 12
            }, 
            "20170404" : {
                "val0" : 5,
                ...
                "valn" : 43
            },
            ...
         },
         "key11" : {...},
         ...
     },
     "key01" : {...},
     "key02" : {...},
     ...
}

我想使用jq将树分解为扁平列表,其格式如下所示。此过程应选择层次结构中的一个特定键,日期以及树中该日期的每个实例合并该日期的值,同时根据值的位置使其键具有唯一性在树上:

[
    {
        "date" : "20170405",
        "key00.key10.val0" : 10,
        ...
        "key00.key10.valn" : 12
    },
    {
        "date" : "20170404",
        "key00.key10.val0" : 10,
        ...
        "key00.key10.valn" : 12
    },
    ...
    {
        "date" : "20170403",
        "key0n.key1n.val0" : 10,
        ...
        "key0n.key1n.valn" : 12
    },
]

知道嵌套结构,假设它是刚性的,我用Perl中的一组for循环执行了这个。但如果结构发生变化,程序就会中断。此外,对于每个层次结构,我都需要一个for循环。您将如何使用jq的语言递归遍历此树?

(我想使用jq,因为我已经在使用它将第一个代码清单中的格式合并到一起,所以我认为我可以构建它。合并很简单:jq -s 'reduce .[] as $x ({}, . * $x)' *.json > merged.json)< / p>

2 个答案:

答案 0 :(得分:1)

这可能看起来像是一个艰难的过程,但为了使事情更容易理解,让我们从一个辅助函数开始,当给定一个字符串数组和一个值时,它本质上会创建一个JSON对象,通过创建一个使用连接字符从数组中键:

# input: [arrayOfStrings, value]
def squish(joinchar): { (.[0] | join(joinchar)): .[1] };

例如,[["a","b"], 10] | squish(".")会发出{"a.b", 10}

问题解决方案的其余部分基于内置过滤器pathsgroup_by,这些过滤器在其他地方有记录,但简而言之,paths会发出字符串流表示路径的数组;然后加上相关的值。然后使用group_by按日期对[path,value]数组进行分组。最后,结果根据要求进行格式化。

. as $in
| [paths 
   | select(length==4)
   | . as $path
   | [ $path, ($in|getpath($path)) ] ]
| group_by( .[0][2] | tonumber )   # sort by numeric value
| map( {date: .[0][0][2] }
        + ( map( del(.[0][2]) | squish(".") ) | add) )

注意事项

  1. 上述解决方案按日期对全局路径进行分组,这似乎符合除样本输出数据之外的要求。

  2. 如果数据与给定的样本不同,则可能必须修改上面使用的select(length==4)标准。

答案 1 :(得分:0)

以下是使用 to_entries 获取数据的解决方案,将其放入 setpath 接受的表单, group_by 按日期和使用 setpath 减少以构建最终表单。

您可以逐步了解其工作原理。首先从

开始
  to_entries
| .[]
| .key as $k1
| ( .value | to_entries
           | $k1, .[] )

转到第一把钥匙。用我的测试数据给我

"key00"
{
  "key": "key10",
  "value": {
    "20170405": {
      "val0": 10,
      "valn": 12
    },
    "20170404": {
      "val0": 5,
      "valn": 43
    }
  }
}
"key01"
...

然后向下钻取一点以获得下一个键

  to_entries
| .[]
| .key as $k1
| ( .value | to_entries
           | .[]
           | .key as $k2
           | ( .value | to_entries
                      | $k1, $k2, .[] ) ) 

给出了

"key00"
"key10"
{
  "key": "20170405",
  "value": {
    "val0": 10,
    "valn": 12
  }
}
{
  "key": "20170404",
  "value": {
    "val0": 5,
    "valn": 43
  }
}
"key01"
"key11"
...

然后多一点来得到日期和最终值

  to_entries
| .[]
| .key as $k1
| ( .value | to_entries
           | .[]
           | .key as $k2
           | ( .value | to_entries
                      | .[]
                      | .key as $d
                      | ( .value | to_entries
                                 | .[]
                                 | [$d, [$k1, $k2, .key], .value] ) ) )

现在我们已经

[
  "20170405",
  [
    "key00",
    "key10",
    "val0"
  ],
  10
]
[
  "20170405",
  [
    "key00",
    "key10",
    "valn"
  ],
  12
]
...

将其放回数组并使用 group_by reduce setpath

[
  to_entries
| .[]
| .key as $k1
| ( .value | to_entries
           | .[]
           | .key as $k2
           | ( .value | to_entries
                      | .[]
                      | .key as $d
                      | ( .value | to_entries
                                 | .[]
                                 | [$d, [$k1, $k2, .key], .value]
                        )                
             )
   )
]
| group_by(.[0])
| .[]
| .[0][0] as $d
| reduce .[] as $e (
      {date:$d}
    ; setpath([$e[1] | join(".")]; $e[2])
  )

得到最终答案

{
  "date": "20170404",
  "key00.key10.val0": 5,
  "key00.key10.valn": 43
}
{
  "date": "20170405",
  "key01.key11.val1": 1,
  "key00.key10.valn": 12,
  "key00.key10.val0": 10,
  "key01.key11.val2": 2
}
{
  "date": "20170406",
  "key01.key11.val0": 0,
  "key01.key11.val9": 9
}
...