Home > Enterprise >  How do I provide an incrementing counter in place of an existing JSON value using jq
How do I provide an incrementing counter in place of an existing JSON value using jq

Time:12-04

I have an JSON file similar to this:

{
    "version": "2.0",
    "stage" : {
        "objects" : [
            {
                "foo" : 1100,
                "bar" : false,
                "id" : "56a983f1-8111-4abc-a1eb-263d41cfb098"
            },
            {
                "foo" : 1100,
                "bar" : false,
                "id" : "6369df4b-90c4-4695-8a9c-6bb2b8da5976"
            }],
        "bish" : "#FFFFFF"
    },
    "more": "abcd"
} 

I would like the output to be exactly the same, with the exception of an incrementing integer in place of the "id" : "guid" - something like:

{
    "version": "2.0",
    "stage" : {
        "objects" : [
            {
                "foo" : 1100,
                "bar" : false,
                "id" : 1
            },
            {
                "foo" : 1100,
                "bar" : false,
                "id" : 2
            }],
        "bish" : "#FFFFFF"
    },
    "more": "abcd"
} 

I'm new to jq. I can set the id's to a fixed integer with .stage.objects[].id |= 1.

{
  "version": "2.0",
  "stage": {
    "objects": [
      {
        "foo": 1100,
        "bar": false,
        "id": 1
      },
      {
        "foo": 1100,
        "bar": false,
        "id": 1
      }
    ],
    "bish": "#FFFFFF"
  },
  "more": "abcd"
}

I can't figure out the syntax to make the assigned number iterate.

I tried various combinations of map, reduce, to_entries, foreach and other strategies mentioned in answers to similar questions but the data in those examples always consisted of something simple.

CodePudding user response:

You can exploit the fact that to_entries on arrays uses the index as "key", then modify your value:

.stage.objects |= (to_entries | map(.value.id = .key   1 | .value))

or

.stage.objects |= (to_entries | map(.value  = {id: (.key   1)} | .value))

Output:

{
  "version": "2.0",
  "stage": {
    "objects": [
      {
        "foo": 1100,
        "bar": false,
        "id": 1
      },
      {
        "foo": 1100,
        "bar": false,
        "id": 2
      }
    ],
    "bish": "#FFFFFF"
  },
  "more": "abcd"
}

CodePudding user response:

Here's a variant using reduce to iterate over the keys:

.stage.objects |= reduce keys[] as $i (.; .[$i].id = $i   1)
{
  "version": "2.0",
  "stage": {
    "objects": [
      {
        "foo": 1100,
        "bar": false,
        "id": 1
      },
      {
        "foo": 1100,
        "bar": false,
        "id": 2
      }
    ],
    "bish": "#FFFFFF"
  },
  "more": "abcd"
}

Demo


Update:

Is there a way to make the search and replace go deep? If the items in the objects array had children arrays with id's, could they be replaced as well?

Of course. You could enhance the LHS of the update to also cover all .children arrays recursively using recurse(.[].children | arrays):

(.stage.objects | recurse(.[].children | arrays)) |=
  reduce keys[] as $i (.; .[$i].id = $i   1)

Demo

Note that in this case each .children array is treated independently, thus numbering starts from 1 in each of them. If you want a continuous numbering instead, it has to be done outside and brought down into the iteration. Here's a solution gathering the target paths using path, numbering them using to_entries, and setting them iteratively using setpath:

reduce (
  [path(.stage.objects[] | recurse(.children | arrays[]).id)] | to_entries[]
) as $i (.; setpath($i.value; $i.key   1))

Demo

  • Related