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

使用jq将嵌套的JSON文件分解为具有唯一键的平面列表,json,key,grouping,jq,flatten,Json,Key,Grouping,Jq,Flatten,给定以下格式的JSON文件: { "key00" : { "key10" : { "20170405" : { "val0" : 10, ... "valn" : 12 }, "20170404" : { "val0" : 5, ...

给定以下格式的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

这看起来很艰难,但为了让事情更容易理解,让我们从一个helper函数开始,该函数在给定一个字符串数组和一个值时,通过使用连接字符从数组中创建一个键,基本上将生成一个JSON对象:

# input: [arrayOfStrings, value]
def squish(joinchar): { (.[0] | join(joinchar)): .[1] };
例如,
[[“a”,“b”,10]| squish(“.”
发出
{“a.b”,10}

该问题的其余解决方案基于内置过滤器
路径
分组依据
,这些过滤器在别处有记录,但简而言之,
路径
会发出表示路径的字符串数组流;然后将关联的值附加到上。然后使用
group\u 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) )
警告
  • 上述解决方案按日期对路径进行全局分组,这似乎符合要求,但样本输出数据除外

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


  • 这里是一个解决方案,使用来获取数据,将其放入一个表单中setpath将接受,group\u by按日期组织,并reduce使用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
    ]
    ...
    
    将其放回一个数组中,并使用groupbyreducesetpath

    [
      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
    }
    ...
    

    这很有趣。我正在消化和理解它。但是,w.r.t.你的警告1,是的,这是示例数据中的一个输入错误。我已经纠正了。这为我如何操作输入数据打开了一扇全新的大门。非常感谢。我让它工作,我基本上理解了整个序列。我没有得到的是索引。e、 g.
    groupby([0][2])
    ,文档不清楚,我假设这意味着查看数组中的第一个元素和它的第三个元素,而不是按该元素对数组第一级中的其余元素进行分组。类似于
    [*][2]
    的东西,如果支持的话?
    [0][2]
    只是
    [0].[2]
    的缩写。如果您不理解文档和示例,那么我建议使用jq和/或jqplay(例如,为了更好地理解内置过滤器或管道)。战略性地进行
    调试
    通常也很有用。