Home > Blockchain >  Transforming json file using jq so data ends up under a common object
Transforming json file using jq so data ends up under a common object

Time:08-26

I have some json data i get from an API that i need to transform using jq into another json format for later use with ansible as part of an inventory script.

What i have is something like:

{
  "results": [
    {
      "name": "hostname1",
      "key1": "somevalue1",
      "key2": "somevalue2",
      "key3": "somevalue3"
    },
    {
      "name": "hostname2",
      "key1": "somevalue12",
      "key2": "somevalue22",
      "key3": "somevalue32"
    },
    {
      "name": "hostname3",
      "key1": "somevalue13",
      "key2": "somevalue23",
      "key3": "somevalue33"
    }
  ]
}

and i need to transform this to look like this:

{
  "_meta": {
    "hostvars": {
      "hostname1": {
        "name": "hostname1",
        "key1": "somevalue1",
        "key2": "somevalue2",
        "key3": "somevalue3"
      },
      "hostname2": {
        "name": "hostname2",
        "key1": "somevalue12",
        "key2": "somevalue22",
        "key3": "somevalue32"
      },
      "hostname3": {
        "name": "hostname3",
        "key1": "somevalue13",
        "key2": "somevalue23",
        "key3": "somevalue33"
      }
    }
  }
}

Things i have not been able to figure out. If i use something like:

cat input.json | jq '{_meta: { hostvars: { (.results[].name): (.results[]) } }}'

Then _meta and hostvars is repeated for each object in the input and that is not at all what i want, i need a common "header" and then the data under there.

Ideally i would like to also exclude the "name" part in the output since it is already used and duplicated, but this is just a bonus.

Advice on how to do this? or is the filter in jq always run against one object at a time? I experimented a bit with --slurp but didn't get anywhere

CodePudding user response:

Using .results[] twice will iterate over the same list twice, giving you the cartesian product of each host with each of the other objects ( 3 x 3 = 9 ). You need to reference it once!

You can do something like below. The key to the logic is forming an array of objects, firstly by making the key name as .name and the value as the whole sub-object inside.

Once you have the array, you can un-wrap into a comma-separated list of objects using add.

{ _meta : { hostvars: ( ( .results | map( { ( .name ) : . } ) | add ) ) } } 

Demo - jqplay

CodePudding user response:

You'll probably want to take advantage of jq pipes.

cat input.json | jq '{ _meta : { hostvars : (.results | to_entries | map({key : .value.name, value :  del(.value | .name).value }) | from_entries) }}'

First, form entries from the results:

.results | to_entries

Then, map these entries to a new set of entries where the key is your name, removing the the .name key as you do so:

... | map({key : .value.name, value :  del(.value | .name).value })

Then, form an object from the entries:

... | from_entries

NOTE: See Inian's answer for why the array syntax used by the OP does not work.

  • Related