json jq:如何为一个大的流媒体用例定制atomize函数?

66bbxpm5  于 2023-03-09  发布在  其他
关注(0)|答案(2)|浏览(131)

我有一个非常大的文件,其结构如下:

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

stats中只有两个键:dailymonthly,它们都包含大量的键值对。
我想分别对.stats.daily.stats.monthly中的所有键-值对进行流处理。
我不知道如何编辑cookbook中的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"))

有人能解释一下它是如何工作的,以及如何针对我的用例修复它吗?谢谢

pprl5pva

pprl5pva1#

既然您已经指出要将“每日”值与“每月”值分开处理,那么让我们关注前者。
为此,让我们仅使用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 );
vvppvyoh

vvppvyoh2#

jq流解析器(由--stream命令行选项激活)可以经济地处理非常大的JSON输入,但即使是相对简单的任务也可能使用起来相当棘手,因此命令行实用程序jm and jm.py(前者基于“JSON Machine”,后者使用流Python解析器)可能会很有用。
无论如何,下面是使用这些实用程序完成当前任务的一些非常简单的解决方案:

< $IN jm.py -s -i stats
{"daily": {"k1": {"etc": "..."}, "k2": {"etc": "..."}, "etc": "...", "kN": {"etc": "..."}}}
{"monthly": {"p1": {"etc": "..."}, "p2": {"etc": "..."}, "etc": "...", "pN": {"etc": "..."}}}
< $IN jm.py -i stats --values
{"k1": {"etc": "..."}, "k2": {"etc": "..."}, "etc": "...", "kN": {"etc": "..."}}
{"p1": {"etc": "..."}, "p2": {"etc": "..."}, "etc": "...", "pN": {"etc": "..."}}
< $IN jm -s --pointer /stats
{"daily": {"k1":{"etc":"..."},"k2":{"etc":"..."},"etc":"...","kN":{"etc":"..."}}}
{"monthly": {"p1":{"etc":"..."},"p2":{"etc":"..."},"etc":"...","pN":{"etc":"..."}}}
< $IN jm --pointer /stats
{"k1":{"etc":"..."},"k2":{"etc":"..."},"etc":"...","kN":{"etc":"..."}}
{"p1":{"etc":"..."},"p2":{"etc":"..."},"etc":"...","pN":{"etc":"..."}}

请注意,jm和www.example.com都jm.py是为处理非常大的JSON输入而设计的。
免责声明:jm和www.example.com脚本都是我写jm.py的,但它们只是分别 Package 了JSON机器和ijson。

相关问题