Home > front end >  Mapping an array of objects to plain array with jq
Mapping an array of objects to plain array with jq

Time:08-27

I'm working on implementing translations in my app, however the format of my translations is not usable for me. I made a shell script that uses jq to try to modify this array, but I cannot get the output that I desire.

The JSON I get from my service looks something like this.

{
  "result": {
    "terms": [
      {
        "term": "title",
        "translation": {
          "content": "Welcome to {{user}}"
        }
      },
      {
        "term": "car",
        "translation": {
          "content": {
              "one": "car",
              "other": "cars"
          }
        }
      }
    ]
  }
}

The output that I want is something like this.

{
  "title": "Welcome to {{user}}",
  "car_one": "car",
  "car_other": "cars",
}

I've managed to strip away the uneeded parts of my objects, but I can't figure out how to append something to they key, e.g. turning "car" into "car_one". Or actually just adding the keys properly to the array.

This is currently where I'm at https://jqplay.org/s/P6KIEVX5sWp

CodePudding user response:

The requirements aren't entirely clear to me, but the following does follow the logic of your approach and does produce the required output:

   .result
   | [.terms[]
      | if .translation.content|type == "string" then {title: .translation.content}
        else .term as $term
        | .translation
        | (.content|keys) as $keys
        | ([$keys[] as $key | {($term   "_"   $key): .content[$key]}] | add) 
        end ]
  | add

CodePudding user response:

Probably not the most efficient solution, but this oughta work and is kinda readable:

.result.terms
| map(
    (select(.translation.content | type == "object")
        | .term as $term | .translation.content | to_entries[] | .key |= "\($term)_\(.)"),
    (select(.translation.content | type == "string")
        | { key: .term, value: .translation.content })
)
| from_entries

Or with if-then-else:

.result.terms
| map(
    if .translation.content | type == "object" then
        .term as $term | .translation.content | to_entries[] | .key |= "\($term)_\(.)"
    else
        { key: .term, value: .translation.content }
    end
)
| from_entries

Cleverly place (and name) the variable and you get down to:

.result.terms
| map(.term as $key
    | .translation.content
    | if type == "object" then
         to_entries[] | .key |= "\($key)_\(.)"
    else
        { $key, value: . }
    end
)
| from_entries

Even more concise by combining the optional/error suppression operator with the alternative operator:

.result.terms
| map(.term as $key
    | .translation.content
    | (to_entries[] | .key |= "\($key)_\(.)")? // { $key, value: . }
)
| from_entries

Or, if you prefer (so many possibilities):

.result.terms
| map(
    .term as $key
    | .translation.content as $value | $value
    | (to_entries[] | .key |= "\($key)_\(.)")? // { $key, $value }
)
| from_entries

"\($key)_\(.)" is string interpolation and equivalent to ($key "_" .)

  • Related