Per48edjes
Per48edjes

Reputation: 113

Update deeply nested field with value from higher-level object in JQ

Given the following input JSON:

{
  "version": 2,
  "models": [
    {
      "name": "first_table",
      "tests": [
        {
          "dbt_utils.equal_rowcount": {
            "compare_model": null
          }
        }
      ]
    },
    {
      "name": "second_table",
      "tests": [
        {
          "dbt_utils.equal_rowcount": {
            "compare_model": null
          }
        }
      ]
    }
  ]
}

How would I, using jq, replace the null (i.e., the value of "compare_model") with the value from the "name" key? Note that the key-value pairs in question here are not at the same level in the hierarchy: the former is nested in an object in an array, and it is this array that is at the same level as the latter.

For example, the output file should read:

{
  "version": 2,
  "models": [
    {
      "name": "first_table",
      "tests": [
        {
          "dbt_utils.equal_rowcount": {
            "compare_model": "first_table"
          }
        }
      ]
    },
    {
      "name": "second_table",
      "tests": [
        {
          "dbt_utils.equal_rowcount": {
            "compare_model": "second_table"
          }
        }
      ]
    }
  ]
}

FWIW, this is an intermediate step in some YAML (via yq, the Python wrapper variety of jq as opposed to the go variant) wrangling I'm doing on DBT config files.

(Bonus points if you can wrap the replacement text with parentheses and/or prefix it without breaking out of jq. :D If not, no worries -- this step I can do with another program.)

Needless to say, but your help is very much appreciated!


Upvotes: 1

Views: 1250

Answers (2)

peak
peak

Reputation: 116740

The key to a simple solution is to use |=, e.g.

.models |= 
  map(.name as $name
      | (.tests[]."dbt_utils.equal_rowcount".compare_model =
         $name))

To wrap the replacement value in parentheses, just add them:

.models |= 
  map("(\(.name))" as $name
      | (.tests[]."dbt_utils.equal_rowcount".compare_model =
         $name))

If you want the replacement to be conditional on the existing value being null, you could perhaps (depending on the exact requirements) use //=.

Using //= and walk

Here's another take on the problem:

.models
  |= map("(\(.name))" as $name
         | walk(if type=="object" and has("compare_model") 
                then .compare_model //= $name
                else . end))

Upvotes: 3

oguz ismail
oguz ismail

Reputation: 50750

That the fields are not at the same level doesn't really matter here.

.models[] |= (.tests[]."dbt_utils.equal_rowcount".compare_model = "(\(.name))")

Online demo

Upvotes: 2

Related Questions