如何根据输入

时间:2018-05-02 10:03:35

标签: json jq

鉴于此表格中的输入

[
  {
    "DIR" : "/foo/bar/a/b/c",
    "OUT" : "/foo/bar/x/y/z",
    "ARG" : [ "aaa", "bbb", "/foo/bar/a", "BASE=/foo/bar" ]
  },
  {
    "DIR" : "/foo/baz/d/e/f",
    "OUT" : "/foo/baz/x/y/z",
    "ARG" : [ "ccc", "ddd", "/foo/baz/b", "BASE=/foo/baz" ]
  },
  { 
    "foo" : "bar"
  }
]

我正试图找出如何让jq转换成这个:

[
  {
    "DIR" : "BASE/a/b/c",
    "OUT" : "BASE/x/y/z",
    "ARG" : [ "aaa", "bbb", "BASE/a", "BASE=/foo/bar" ]
  },

  {
    "DIR" : "BASE/d/e/f",
    "OUT" : "BASE/x/y/z",
    "ARG" : [ "ccc", "ddd", "BASE/b", "BASE=/foo/baz" ]
  },
  { 
    "foo" : "bar"
  }
]

换句话说,具有"ARG"数组的对象,包含以"BASE="开头的字符串,应使用"BASE="之后的字符串,例如"/foo"替换以“/foo"开头的其他字符串值("BASE=/foo"除外应保持不变”)

我自己甚至都没有找到解决方案,而且在这一点上我不确定jq是否可以完成这项工作。

3 个答案:

答案 0 :(得分:1)

别担心,仅 jq 就可以完成这项任务:

jq 'def sub_base($base): if (startswith("BASE") | not) then sub($base; "BASE") else . end;
    map(if .["ARG"] then ((.ARG[] | select(startswith("BASE=")) | split("=")[1]) as $base
            | to_entries
            | map(if (.value | type == "string") then .value |= sub_base($base)
                  else .value |= map(sub_base($base)) end)
            | from_entries)
        else . end)' input.json

输出:

[
  {
    "DIR": "BASE/a/b/c",
    "OUT": "BASE/x/y/z",
    "ARG": [
      "aaa",
      "bbb",
      "BASE/a",
      "BASE=/foo/bar"
    ]
  },
  {
    "DIR": "BASE/d/e/f",
    "OUT": "BASE/x/y/z",
    "ARG": [
      "ccc",
      "ddd",
      "BASE/b",
      "BASE=/foo/baz"
    ]
  },
  {
    "foo": "bar"
  }
]

答案 1 :(得分:1)

使用jq

#!/usr/bin/jq -f

# fix-base.jq
def fix_base:
  (.ARG[] | select(startswith("BASE=")) | split("=")[1]) as $base
  | .DIR?|="BASE"+ltrimstr($base)
  | .OUT?|="BASE"+ltrimstr($base)
  | .ARG|=map(if startswith($base) then "BASE"+ltrimstr($base) else . end)
  ;

map(if .ARG? then fix_base  else . end)

您可以像这样运行:

jq -f fix-base.jq input.json

或使其成为可执行文件:

chmod +x fix-base.jq
./fix-base.jq input.json

答案 2 :(得分:1)

一些帮助函数使得更容易。第一个是通用的,也许值得你的标准库:

# Returns the integer index, $i, corresponding to the first element
# at which f is truthy, else null
def indexof(f):
  label $out
  | foreach .[] as $x (null; .+1; 
      if ($x|f) then (.-1, break $out) else empty end) // null;

# Change the string $base to BASE using gsub
def munge($base):
  if type == "string" and (test("^BASE=")|not) then gsub($base; "BASE")
  elif type=="array" then map(munge($base))
  elif type=="object" then map_values(munge($base))
  else .
  end;

现在很简单:

map(if has("ARG") 
    then (.ARG|indexof(test("^BASE="))) as $ix
    | if $ix
      then (.ARG[$ix]|sub("^BASE=";"")) as $base | munge($base)
      else . end 
    else . end )

需要注意的一些要点:

  • 您可能希望sub使用gsub而不是munge;
  • 上述解决方案假设您想要更改所有键,而不仅仅是“DIR”,“OUT”和“ARG”
  • 上述解决方案允许包含一次或多次“=”的BASE规范。