Mark Reed
Mark Reed

Reputation: 95335

Turn array of keys and array of values into object

I have, for complicated reasons involving a trip from an Apple plist through xml2json, a number of JSON files with data in this form:

{ 
  "key": [ "key1", "key2", "key3" ],
  "string": [ "value1", "value2", "value3" ]
}

And I would like to convert that to a normal JSON object:

{ 
  "key1": "value1",
  "key2": "value2",
  "key3": "value3"     
}

After some head-banging, I came up with this jq program to do the trick:

jq '. as $d|[range(.key|length)|{"key":$d.key[.],"value":$d.string[.]}]|from_entries'

That works, but it seems a little convoluted. I was wondering if there were a cleaner solution?

This is indeed similar to this question, but the difference is that this is an object with named key and value elements instead just an array containing the two arrays directly.

Upvotes: 1

Views: 178

Answers (2)

jq170727
jq170727

Reputation: 14715

Here is a solution which uses reduce with a state object holding an iteration index and a result object. It iterates over .key setting corresponding values in the result from .string

  .string as $v
| reduce .key[] as $k (
   {idx:0, result:{}}; .result[$k] = $v[.idx] | .idx += 1
  )
| .result

Upvotes: 0

user3899165
user3899165

Reputation:

The script you provide is already pretty good! This variation of your script saves the index into the variable instead of the input object, which feels more natural to read for me. It then creates an array of one key objects and adds them together.

jq '[range(.key | length) as $i | {(.key[$i]): .string[$i]}] | add'

When I first looked at this issue, I though that a zip builtin would improve the situation. Then I remembered: there is already a zip builtin! It's just called transpose. Using it, you can create a script such as this:

jq '[.key, .string] | transpose | map({key: .[0], value: .[1]}) | from_entries'

It seems easier to follow to me as well, although it is quite long too; I assume, however, that the focus is readability and not character count. Of course, you can also mix up both solutions:

jq '[.key, .string] | transpose | map({(.[0]): .[1]}) | add'

Upvotes: 4

Related Questions