0

Given the input in this form

[
  {
    "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"
  }
]

I'm trying to find out how to make jq transform that into this:

[
  {
    "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"
  }
]

In other words, objects having an "ARG" array, containing a string that starts with "BASE=" should use the string after "BASE=", e.g. "/foo" to substitute other string values that start with "/foo" (except the "BASE=/foo" which should remain unchanged")

I'm not even close to finding a solution myself, and at this point I'm unsure that jq alone will do the job.

3 Answers 3

1

With 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)

You can run it like this:

jq -f fix-base.jq input.json

or make it an executable like this:

chmod +x fix-base.jq
./fix-base.jq input.json
Sign up to request clarification or add additional context in comments.

Comments

1

Don't worry, jq alone will do the job:

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

The output:

[
  {
    "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"
  }
]

Comments

1

Some helper functions make the going much easier. The first is generic and worthy perhaps of your standard library:

# 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;

And now the easy part:

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 )

Some points to note:

  • You may wish to use sub rather than gsub in munge;
  • The above solution assumes you want to make the change in all keys, not just "DIR", "OUT", and "ARG"
  • The above solution allows specifications of BASE that include one or more occurrences of "=".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.