Как заменить строки в JSON на JQ на основе ввода - PullRequest
0 голосов
/ 02 мая 2018

С учетом ввода в этой форме

[
  {
    "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 голосов
/ 02 мая 2018

С 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
0 голосов
/ 02 мая 2018

Некоторые вспомогательные функции значительно облегчают работу. Первый типичный и, возможно, достоин вашей стандартной библиотеки:

# 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, которые включают одно или несколько вхождений "=".
0 голосов
/ 02 мая 2018

Не волнуйтесь, 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"
  }
]
...