Search code examples
jsonjqnested-loopslookup

Expand references to sibling values in a JSON using JQ


I have a JSON document holding configurations with macros. I need to expand macros that reference other elements. For simplicity, consider this input document.

[
  {
    "first": "Tim",
    "Full": "{first} {last}",
    "last": "Smith"
  },
  {
    "first": "Jane",
    "Full": "{first} {last}",
    "last": "Doe"
  }
]

Performance is not paramount here so I don't mind blindly checking for every element occurring in every other element.

I worked out the following logic as a proof of concept. But can't figure out how to add a nested loop on $lookup to update the other with_entries value.

jq  '
  .[]
| . as $lookup
| with_entries(
    .value=(
      .value
      | sub("{first}"; $lookup.first)
    )
  )
'

JQ processes streams of values/documents and it feels like I now need to work with two streams. Which I hoped to accomplish by using $lookup for the back reference. Now I am stuck.

SOLUTION oguz ismail provided a great solution for the original question (Please give them a +1) that uses map_values(). It is very clear and I wanted to include it here for reference. You will note that it uses a named group (?<found_key>.*?) in the regular expression (see oniguruma's named group)

map(. as $lookup | map_values(gsub("\\{(?<found_key>.*?)\\}"; $lookup[.found_key])))

I asked how to process when there are non-string elements in the structure. Here is an example that includes an array of colors:

[
  {
    "first": "Tim",
    "Full": "{first} {last}",
    "last": "Smith",
    "colors": ["red", "blue"]
  }
]

oguz ismail provided a solution for this structure as well that only attempts to modify elements that are strings:

map(
  . as $lookup
  | (.[] | strings)
  |= gsub("\\{(?<found_key>.*?)\\}"; $lookup[.found_key])
)

Solution

  • You can use gsub with a named capture group for expanding all the macros in a single run without hardcoding their names. And, with_entries doesn't help at all in this case; use map_values instead.

    map(. as $lookup | map_values(gsub("\\{(?<found_key>.*?)\\}"; $lookup[.found_key])))
    

    Online demo