jq流式传输大型json文件,只获取其属性具有特定值

时间:2016-02-10 06:49:44

标签: json mapreduce jq

我有一些相当大的json文件(~500mb - 4gb压缩),我无法加载到内存中进行操作。所以我在jq。

中使用了--stream选项

例如我的json可能看起来像这样 - 只是更大:

[{
  "id": "0001",
  "type": "donut",
  "name": "Cake",
  "ppu": 0.55,
  "batters": {
    "batter": [{
      "id": "1001",
      "type": "Regular"
    }, {
      "id": "1002",
      "type": "Chocolate"
    }, {
      "id": "1003",
      "type": "Blueberry"
    }, {
      "id": "1004",
      "type": "Devil's Food"
    }]
  },
  "topping": [{
    "id": "5001",
    "type": "None"
  }, {
    "id": "5002",
    "type": "Glazed"
  }, {
    "id": "5005",
    "type": "Sugar"
  }, {
    "id": "5007",
    "type": "Powdered Sugar"
  }, {
    "id": "5006",
    "type": "Chocolate with Sprinkles"
  }, {
    "id": "5003",
    "type": "Chocolate"
  }, {
    "id": "5004",
    "type": "Maple"
  }]
}, {
  "id": "0002",
  "type": "donut",
  "name": "Raised",
  "ppu": 0.55,
  "batters": {
    "batter": [{
      "id": "1001",
      "type": "Regular"
    }]
  },
  "topping": [{
    "id": "5001",
    "type": "None"
  }, {
    "id": "5002",
    "type": "Glazed"
  }, {
    "id": "5005",
    "type": "Sugar"
  }, {
    "id": "5003",
    "type": "Chocolate"
  }, {
    "id": "5004",
    "type": "Maple"
  }]
}, {
  "id": "0003",
  "type": "donut",
  "name": "Old Fashioned",
  "ppu": 0.55,
  "batters": {
    "batter": [{
      "id": "1001",
      "type": "Regular"
    }, {
      "id": "1002",
      "type": "Chocolate"
    }]
  },
  "topping": [{
    "id": "5001",
    "type": "None"
  }, {
    "id": "5002",
    "type": "Glazed"
  }, {
    "id": "5003",
    "type": "Chocolate"
  }, {
    "id": "5004",
    "type": "Maple"
  }]
}]

如果这是我可以在内存中保存的文件类型,并且我想选择只有击球员类型"巧克力"的对象,我可以使用:

cat sample.json | jq '.[] | select(.batters.batter[].type == "Chocolate")'

我只会使用ids "0001""0003"

取回整个对象

但是通过流式传输,我知道它与众不同。

我正在阅读关于流媒体herehere的jq文档,但我仍然感到很困惑,因为这些示例并未真正展示json的真实世界问题。

即,甚至可以在流式传输路径并识别显着事件后选择整个对象,或者在这种情况下是与某个字符串匹配的属性值?

我知道我可以使用:

cat sample.json | jq --stream 'select(.[0][1] == "batters" and .[0][2] == "batter" and .[0][4] == "type") | .[1]'

给我所有的击球手类型。但有没有办法说:"如果它是巧克力,抓住这个叶子是#34的一部分?

2 个答案:

答案 0 :(得分:1)

命令:

$ jq -cn --stream 'fromstream(1|truncate_stream(inputs))' array_of_objects.json | 
  jq 'select(.batters.batter[].type == "Chocolate") | .id'

输出:

"0001"
"0003"

第一次调用jq会将对象数组转换为对象流。第二种是基于您的调用,可以根据您的需求进行定制。

当然,两个调用可以(也可能应该)合并为一个,但您可能希望使用第一个调用将大文件保存为包含对象流的文件。

顺便说一下,使用以下select

可能会更好
select( any(.batters.batter[]; .type == "Chocolate") )

答案 1 :(得分:0)

这是另一种方法。从流式过滤器filter1.jq开始,它提取您需要处理的记录号和最小属性集。 E.g。

  select(length==2)
| . as [$p, $v]
| {r:$p[0]}
| if   $p[1] == "id"                           then .id   = $v
  elif $p[1] == "batters" and $p[-1] == "type" then .type = $v
  else  empty
  end      

使用

运行此功能
jq -M -c --stream -f filter1.jq bigdata.json

产生类似

的值
{"r":0,"id":"0001"}
{"r":0,"type":"Regular"}
{"r":0,"type":"Chocolate"}
{"r":0,"type":"Blueberry"}
{"r":0,"type":"Devil's Food"}
{"r":1,"id":"0002"}
{"r":1,"type":"Regular"}
{"r":2,"id":"0003"}
{"r":2,"type":"Regular"}
{"r":2,"type":"Chocolate"}

现在将其传输到第二个过滤器filter2.jq,它会对每个记录的那些属性执行所需的处理

foreach .[] as $i (
     {c: null, r:null, id:null, type:null}

   ; .c = $i
   | if .r != .c.r then .id=null | .type=null | .r=.c.r else . end   # control break
   | .id   = if .c.id == null   then .id   else .c.id   end
   | .type = if .c.type == null then .type else .c.type end

   ; if ([.id, .type] | contains([null])) then empty else . end
)
| select(.type == "Chocolate").id

使用类似

的命令
jq -M -c --stream -f filter1.jq bigdata.json | jq -M -s -r -f filter2.jq 

生产

0001
0003

filter1.jqfilter2.jq比您针对此特定问题所需的更多,但它们可以轻松推广。