jq:如何针对大型流用例定制原子化功能?

时间:2019-04-30 13:47:41

标签: json streaming jq

我的文件很大,结构如下:

{ 
  "users": { ... },
  ...
  "stats": {
    "daily": {
      "k1": { ... },
      "k2": { ... },
      ...
      "kN": { ... }
    },
    "monthly": {
      "p1": { ... },
      "p2": { ... },
      ...
      "pN": { ... }
    }
  }
}

stats中只有两个键:dailymonthly,它们都包含大量的键值对。

我想分别在.stats.daily.stats.monthly内部流式传输所有键值对。 如果文件很小,我只需做jq '.stats.daily' myfile.jsonjq '.stats.monthly' myfile.json

我无法弄清楚如何从食谱中编辑atomize函数以执行所需的操作。这是我正在尝试的不起作用的内容:

jq -nc --stream '
  def atomize(s):
    fromstream(foreach s as $in ( {previous:null, emit: null};
      if ($in | length == 2) and ($in|.[0][0]) != .previous and .previous != null
      then {emit: [[.previous]], previous: $in|.[0][0]}
      else { previous: ($in|.[0][0]), emit: null}
      end;
      (.emit // empty), $in) ) ;
  atomize(2|truncate_stream(inputs | select(.[0][0] == "daily"))

有人可以解释一下它的工作原理以及如何针对我的用例进行修复吗?谢谢

1 个答案:

答案 0 :(得分:1)

由于您已表示要分别处理“每日”值和“每月”值,所以我们将重点放在前者上。

为此,我们首先使用fromstreamtruncate_stream

使用与给出的示例类似的输入,但对其进行了调整以使其为有效的JSON:

fromstream( 1|truncate_stream(1|truncate_stream(
  inputs | select( .[0][0] == "stats" and .[0][1] == "daily" ) )) )

会产生:

{"k1":{"a":[1]},"k2":{"a":[1]},"kN":{"a":[1]}}

如果您有jq 1.6,则可以将上述jq过滤器简化为:

fromstream(2|truncate_stream(
  inputs | select( .[0][0:2] == ["stats","daily"] ) ))

现在,我们仅需使用atomize而不是fromstream即可获得所需的结果。例如,使用jq 1.6,我们看到:

atomize(2|truncate_stream(
  inputs | select( .[0][0:2] == ["stats","daily"] ) ))

会产生:

{"k1":{"a":[1]}}
{"k2":{"a":[1]}}
{"kN":{"a":[1]}}

调用

jq -n -c --stream -f program.jq input.json

效率提升

假设输入中的对象没有重复的键,可以简化上述解决方案,以便一旦处理了感兴趣的键,就不再进行进一步的处理。这可以使用下面定义的run/3来实现。流式传输解决方案将变为:

atomize( 1 | truncate_stream( 1 | truncate_stream(
  run( inputs; .[0][0:2]; ["stats", "daily"] ))))

或者使用jq 1.6:

atomize( 2 | truncate_stream(
  run( inputs; .[0][0:2]; ["stats", "daily"] )))

run/3

# emit the first run of items in the stream for which f == $value
def run(stream; f; $value):
  label $done
  | foreach stream as $x ( {};
      ($x | f) as $k
      | if .start then (if $k == $value then . else .stop = true end)
        elif $k == $value then .start = true
        else .
        end;
      if .stop then break $done 
      elif .start then $x
      else empty
      end );