如何使用jq工具集和正则表达式将复杂的JSON过滤到新的JSON对象数组

时间:2017-09-29 13:54:22

标签: json jq

  

首先〜谢谢花时间阅读本文。如果有的话   需要信息或重述请注释,以便我可以改进   这个问题。我是 jq 的新手,感谢您提供的任何帮助。   如果主题中有任何混淆,那是由于我的缺乏   体验 jq 工具。这似乎相当复杂,甚至是部分的   欢迎回答。

背景

我在一系列JSON数组中有一些JSON对象(底部有样本)。对象有许多元素,但只有与“数据”键相关的值才是我感兴趣的。我想输出一个JSON对象数组,其中值根据一些正则表达式规则转换为键/值对。

我想基本上组合多个“数据”值来形成一个关键短语(然后是一个值短语),我需要将其输出为目标对象数组。我相信我应该能够使用正则表达式或一组已知文本(对于关键短语)将文本编译为单个键或值。

当前逻辑

使用:jq-1.5,Mac OS 10.12.6,Bash终端

我检查过的一些事情是通过查看值字段中的(:)冒号(它表示关键短语的结尾)。例如,下面代表关键的“公司地址”:

"data":"Company ",
...
"data": "Address:"
...
{
    "top": 333,
    "left": 520,
    "width": 66,
    "height": 15,
    "font": 5,
    "data":"123 Main St. "
...
"data":"Smallville "
...
"data":"KS "
...
"data":"606101"

在这种情况下,值中的冒号表示附加到以下有用“数据”键的下一个值是地址的开头。

跟踪该值的空格表示找到的下一个数据值是关键短语的延续或我尝试组合成新JSON对象的值短语。

我有一组值可以用来分隔新的JSON对象。基本上,下面的例子允许我创建一个关键的“公司名称”:

...
"data":"Company "
...
"data":"Name"

(请注意,此条目没有冒号,但模式将是要生成的每个新JSON对象的开头)

备注

我可以确定何时达到键或值的结尾取决于它的值是否以空格结尾。 (如果没有空格,那么我认为该值是值短语的结尾并开始捕获下一个关键短语。)

我尝试过的事情

将非常感谢将此逻辑转换为一个或多个有用的jq过滤器的任何帮助。我查看了JQ CookbookJQ Manual,此article,检查了其他SO questions on jq,并评估了其他工具(underscore_cli )。我是jq的新手,我天真的表情一直在失败...

  

我尝试过一些简单的测试来尝试选择感兴趣的值。   (我没能成功地走到json树上去   文本数组下的信息。我有另一个皱纹   多个文本数组。是否可以使用相同的算法   在每个对象数组上执行?)

jq -s '.[]  | select(.data | contains(":"))'
  

jq:error(at:0):无法使用字符串“data”索引数组

示例

标题JSON的示例

[
  {
    "number": 1,
    "pages": 254,
    "height": 1263,
    "width": 892,
    "fonts": [
      {
        "fontspec": "0",
        "size": "-1",
        "family": "Times",
        "color": "#ffffff"
      },
      {
        "fontspec": "1",
        "size": "31",
        "family": "Times",
        "color": "#000000"
      },
      {
        "fontspec": "2",
        "size": "16",
        "family": "Helvetica",
        "color": "#000000"
      },
      {
        "fontspec": "3",
        "size": "13",
        "family": "Times",
        "color": "#237db8"
      },
      {
        "fontspec": "4",
        "size": "17",
        "family": "Times",
        "color": "#000000"
      },
      {
        "fontspec": "5",
        "size": "13",
        "family": "Times",
        "color": "#000000"
      },
      {
        "fontspec": "6",
        "size": "8",
        "family": "Times",
        "color": "#9f97a7"
      },
      {
        "fontspec": "7",
        "size": "10",
        "family": "Times",
        "color": "#9f97a7"
      }
    ],
    "text": [
      {
        "top": 83,
        "left": 60,
        "width": 0,
        "height": 1,
        "font": 0,
        "data": " "
      },
      {
        "top": 333,
        "left": 68,
        "width": 68,
        "height": 15,
        "font": 5,
        "data": "Company "
      },
      {
        "top": 333,
        "left": 135,
        "width": 40,
        "height": 15,
        "font": 5,
        "data": "Name"
      },
      ...(more of these objects with data)
     ]
    ]
  

我希望输出组成键的JSON对象数组   由冒号绑定的键/值对的已知字符串(模式)   (:)表示关键短语的结尾以及其下一个数据值   是价值短语的开始。尾随空间的存在   表示数据值应作为一部分附加   值 - 短语,直到尾随空格不再出现在   数据值。此时,下一个数据值代表开始   另一个关键词。

更新#1:

  

以下答案非常有用。我回到了jq手册   并纳入以下建议。我得到一个字符串,但无法   将数据标记集分成单个字符串。

.[].text |  tostring
  

但是,我看到JSON被转义,其他标签显示   在字符串top, left, right中(及其值)。 ID   喜欢将令牌仅与数据键关联为字符串。   然后在该字符串上运行正则表达式以解析出一个集合   可以定义键和值的JSON对象。

2 个答案:

答案 0 :(得分:2)

因此,从我可以告诉您要做的事情来看,您正在尝试获取所有"data"元素并将它们连接成一个字符串。

应该足够简单:

[.. | .data? | select(. != null) | tostring] | join("")

没有足够的示例数据来了解数据“分组”的开始位置的开始和结束位置。但假设根数组中的每个项目都是单个短语,请在执行搜索之前先选择每个项目(或映射它们):

map([.. | .data? | select(. != null) | tostring] | join(""))

如果最终你想要将数据位解析为json对象,那就不太遥远了:

map(
    [.. | .data? | select(. != null) | tostring]
        | join("")
        | split(":") as [$key,$value]
        | {$key,$value}
) | from_entries

答案 1 :(得分:1)

您可能需要考虑使用jq Streaming。使用示例数据,以下过滤器选择“数据”属性的路径:

  tostream
| select(length==2) as [$p,$v]
| select($p[-1]=="data")
| [$p,$v]

如果这是filter.jq,您的样本数据在data.json命令

$ jq -Mc -f filter.jq data.json

产生

[[0,"text",0,"data"]," "]
[[0,"text",1,"data"],"Company "]
[[0,"text",2,"data"],"Name"]

通过此,您可以看到您的数据在路径.[0].text[0].data.[0].text[1].data.[0].text[2].data中包含信息。

您可以使用reduce在此基础上构建,以根据尾随空格的存在将值收集到组中。使用您的数据以下过滤器

reduce (
    tostream
  | select(length==2) as [$p,$v]
  | select($p[-1]=="data")
) as [$p,$v] (
    [""]
  ; .[-1] += $v
  | if $v|endswith(" ")|not then . += [""] else . end
)
| map(select(. != ""))

产生

[" Company Name"]

此示例仅将数据分组到列表中。如果需要,您可以使用更复杂的缩减。

以下是您可以试用的Try it online!链接。

为了更进一步,我们使用以下示例数据:

[
    { "data":"Company " },
    { "data": "Address:" },
    { "data":"123 Main St. " },
    { "data":"Smallville " },
    { "data":"KS " },
    { "data":"606101" }
]

过滤器将生成

["Company Address:","123 Main St. Smallville KS 606101"]

要将其转换为对象,您可以添加另一个reduce。例如,此过滤器

  reduce (
      tostream
    | select(length==2) as [$p,$v]
    | select($p[-1]=="data")
  ) as [$p,$v] (
      [""]
    ; .[-1] += $v
    | if $v|endswith(" ")|not then . += [""] else . end
  )
| map(select(. != ""))
| reduce .[] as $e (
    {k:"", o:{}}
  ; if $e|endswith(":") then .k = $e[:-1] else .o[.k] += $e end
  )
| .o

产生

{"Company Address":"123 Main St. Smallville KS 606101"}

最后一件事:在这一点上,过滤器变得非常大,因此重构一点并将其分解为functions是有意义的,这样就可以更容易地进行管理和扩展。 e.g。

  def extract:
    [   tostream
      | select(length==2) as [$p,$v]     # collect values for
      | select($p[-1]=="data")           # paths to "data"
      | $v                               # in an array
    ]
  ;

  def gather:
    reduce .[] as $v (
        [""]                             # state: list of grouped values
      ; .[-1] += $v                      # add value to last group
      | if $v|endswith(" ")|not          # if the value ended with " "
        then . += [""]                   # form a new group
        else .
        end
    )
    | map(select(. != ""))               # produce final result
  ;

  def combine:
      reduce .[] as $e (
        {k:"", o:{}}                     # k: current key o: combined object
      ; if $e|endswith(":")              # if value ends with a ":"
        then .k = $e[:-1]                #   use it as a new current key
        else .o[.k] += $e                # otherwise add to current key's value
        end
      )
    | .o                                 # produce the final object
  ;

    extract                              # extract "data" values
  | gather                               # gather into groups
  | combine                              # combine into an object