Home > other >  Modify value in JSON with jq: prepend value with name of parent key
Modify value in JSON with jq: prepend value with name of parent key

Time:03-10

The idea is that I have an object that is part of an array that itself is the value of a parent key. I want to use the value of that key to alter the values in the original child object.

This is what I came up with, and while it works, I can't help but think there is a better way. It's so unintuitive, it took me at least 3 hours of trial end error to nail down the syntax and remove errors.

echo '{"red": [{"fruit": "apple", "grows_on":"tree"}, {"fruit":"raspberry","grows_on":"vine"}], "green": [{"fruit": "apple", "grows_on":"tree"}, {"fruit":"gooseberry","grows_on":"bush"}]}' | 
jq '
  walk(
    if type=="object" then 
      with_entries(
        if .key == "red" then
          with_entries(
            walk(
              if type=="object" then 
                with_entries(
                  if .key=="fruit" then 
                    .value= "red"   "_"   .value 
                  else . end
                ) 
              else . end
            )
          )
        elif .key == "green" then
          with_entries(
            walk(
              if type=="object" then 
                with_entries(
                  if .key=="fruit" then 
                    .value= "green"   "_"   .value 
                  else . end
                ) 
              else . end
            )
          ) 
        else . end
      )
    else . end
  )'

{
  "red": [
    {
      "fruit": "red_apple",
      "grows_on": "tree"
    },
    {
      "fruit": "red_raspberry",
      "grows_on": "vine"
    }
  ],
  "green": [
    {
      "fruit": "green_apple",
      "grows_on": "tree"
    },
    {
      "fruit": "green_gooseberry",
      "grows_on": "bush"
    }
  ]
}

CodePudding user response:

You could use with_entries (which itself is a shortcut to to_entries | map(…) | from_entries) to deconstruct each field into a key-value pair, manipulate to your liking with access to key and value, and reconstruct it again. The inner filter saves the key into a variable, descends into each value's fruit and updates |= its name, according to the key and its cuurent name ..

jq 'with_entries(.key as $key | .value[].fruit |= $key   "_"   .)'
{
  "red": [
    {
      "fruit": "red_apple",
      "grows_on": "tree"
    },
    {
      "fruit": "red_raspberry",
      "grows_on": "vine"
    }
  ],
  "green": [
    {
      "fruit": "green_apple",
      "grows_on": "tree"
    },
    {
      "fruit": "green_gooseberry",
      "grows_on": "bush"
    }
  ]
}

Demo

If you only want to update a top-level object's fields named red or green, and the value array's object item's field named fruit (this is what your checks are doing), you can employ objects (which is a shortcut to select(type == "object")) in combination with appropriate checks.

jq '
  objects |= with_entries(.key as $key | if IN("red", "green"; $key) then
    (.value[] | objects.fruit // empty) |= $key   "_"   . 
  else . end)
'

Demo

  • Related