Search code examples
jsonjq

copy a value to another entry in the same element


There are plenty of examples of updating a value in a nested entry in jq, but I think I have a unique question, because the value I want to provide is already in another part of the same entry.

Given the following input:

{
  "items": [
    {
      "name": "first"
    },
    {
      "name": "second"
    }
  ]
}

I want to produce this output:

{
  "items": [
    {
      "name": "first",
      "value": "first"
    },
    {
      "name": "second",
      "value": "second"
    }
  ]
}

In other words, I want to copy the value of .name to the value of .value for each entry in items[].

Assignment is easy enough if the value is static. jq '.items[].value |= "x"

But since the value is dynamic, I always get stuck either at the wrong context level:

$ jq '.items[].value|=.name' <<< '{"items":[{"name": "first"},{"name": "second"}]}'
{
  "items": [
    {
      "name": "first",
      "value": null
    },
    {
      "name": "second",
      "value": null
    }
  ]
}

Or with the wrong output context:

jq '.items[]|(.value=.name)' <<< '{"items":[{"name": "first"},{"name": "second"}]}'
{
  "name": "first",
  "value": "first"
}
{
  "name": "second",
  "value": "second"
}

How can I make the change within the entries, for each entry, but still output the whole thing from the top level?


Solution

  • When performing an update assignment |=, the context of the RHS is matched to the one from the LHS. Thus, with .items[].value |= .name, the evaluation of .name is based on the (at that point not yet existing) value of .value, and can produce nothing but null.

    Using just the pipe combinator | as in .items[] | (.value = .name) provides both .value and .name with the right context, but the previous context (the document level you want to return) is lost, so the local context (the items without the surrounding array) becomes the result.

    To access different contexts (here, for reading and writing) while keeping another outer one, update on a level that is common to both (the reading and the writing context), and traverse as necessary inside the applied filter on the RHS (going into .value and .name in this case).

    Using your latter attempt, individually updating each item of the .items array:

    .items[] |= (.value = .name)
    

    Demo

    Or having a map filter update the entire array at once:

    .items |= map(.value = .name)
    

    Demo

    In the end, both perform the same operations of course, resulting in:

    {
      "items": [
        {
          "name": "first",
          "value": "first"
        },
        {
          "name": "second",
          "value": "second"
        }
      ]
    }