Simple JSON manipulation with JQ

Last edited 2023-11-11

Either You have a stringified JSON from somewhere, and You want it to look fancy, or only some fields from huge JSON are needed - jq to the rescue.
TLDR: start here
jq cheat sheet

Simplest case make stringified JSON great again

As simple as piping stringified JSON to jq would produce a nicely formatted output in terminal, like:

echo '{"id":1000,"name":"Dmitry","country":"ME","timeZone":"UTC+X","matches":{}}' | jq
{
  "id": 1000,
  "name": "Dmitry",
  "country": "ME",
  "timeZone": "UTC+X",
  "matches": {}
}

You can continue piping it further to next application or a file, as usually with console applications. To see real benefits we need to add “query” to jq. Query is added in single quotes, as jq 'some query'. Some of allowed “special” symbols are: {} for dictionary, [] for an array.

Take only certain JSON keys

We can get both keys & values or only value we care for. Syntax for both key & value is jq '{key1, key2}'

echo '{"id":1000,"name":"Dmitry","country":"ME","timeZone":"UTC+X","matches":{}}' | jq '{id, name}'
{
  "id": 1000,
  "name": "Dmitry"
}

and for value - jq '.key1, key2'.

echo '{"id":1000,"name":"Dmitry","country":"ME","timeZone":"UTC+X","matches":{}}' | jq '.id, .name'
1000
"Dmitry"

Take only certain deeply nested JSON keys

Nested property matches is an object itself and may contain key-values. Like here:

{
  "id": 1000,
  "name": "Dmitry",
  "country": "ME",
  "timeZone": "UTC+X",
  "matches": {
    "infoSec": false,
    "paySec": [
      {
        "id": 12
      },
      {
        "id": 998
      }
    ]
  }
}

Say we care only for paySec, the query to get both keys & values would be:
jq '.matches | {paySec}' we piping inside query with |.

echo '{"id":1000,"name":"Dmitry","country":"ME","timeZone":"UTC+X","matches":{"infoSec":false,"paySec":[{"id":12},{"id":998}]}}' | jq '.matches | {paySec}'
{
  "paySec": [
    {
      "id": 12
    },
    {
      "id": 998
    }
  ]
}

and for values only:
jq '.matches | .paysec'

echo '{"id":1000,"name":"Dmitry","country":"ME","timeZone":"UTC+X","matches":{"infoSec":false,"paySec":[{"id":12},{"id":998}]}}' | jq '.matches | .paySec'
[
  {
    "id": 12
  },
  {
    "id": 998
  }
]

Take only certain keys from different JSON nesting levels

Real life scenario most likely would require having a user id from top level and his matches on paysec from nested properties, omitting other key-values. The tricky part is that we need to make a remap in a query with :. So the query syntax would be be: jq '{id, matches: matches | {paysec}}'

echo '{"id":1000,"name":"Dmitry","country":"ME","timeZone":"UTC+X","matches":{"infoSec":false,"paySec":[{"id":12},{"id":998}]}}' | jq '{id, matches: .matches | {paySec}}'
{
  "id": 1000,
  "matches": {
    "paySec": [
      {
        "id": 12
      },
      {
        "id": 998
      }
    ]
  }
}

Once we started remapping, we can flatten is further with by making matches -> paySecMatches and skipping the paySec key. The query: jq '{id, paySecMatches: .matches | .paySec}'

echo '{"id":1000,"name":"Dmitry","country":"ME","timeZone":"UTC+X","matches":{"infoSec":false,"paySec":[{"id":12},{"id":998}]}}' | jq '{id, paySecMatches: .matches | .paySec}' 
{
  "id": 1000,
  "paySecMatches": [
    {
      "id": 12
    },
    {
      "id": 998
    }
  ]
}

Accessing and flattening Arrays

Need to add [] as an array symbol to a key, to tell jq we are accessing or creating an array.
To flatten such array { list: [ { id: 1 },{ id: 2 } ] }
into {flatList: [1, 2]} we do: jq '{list: [.list[] | .id]}', what stands for: “give Me a dictionary, with single property flatList, that is an array, consisting of values from list array’s values gotten by id key”.

echo '{"list": [{"id":1},{"id":2}]}' | jq '{flatList:[.list[] | .id]}'
{
  "flatList": [
    1,
    2
  ]
}

Wrap up

jq allows even select type queries and math, but never had to dive that deep. While the syntax takes time to grasp, still better than to write javascript throw-away code.