2

I want to modify the following json using variables specified in a Linux bash shell using jq.

var1="red"
var2="european.flowers"
var3="european_vegetables"
var4="20"

My json:

{
 "plants": {
  "flowers.small": {
    "colour": "",
    "age": "",
    "vegetables": {
     "root": "",
     "height": ""
    }
  }
 }
}

I want to modify the json using variables in jq:

{
 "plants": {
  "${var2}": {
    "colour": "${var1}",
    "age": "",
    "${var3}": {
     "root": "",
     "height": "${var4}"
    }
  }
 }
}

I am attempting to just set a field value from a variables:

Command:

cat myjson.json|jq '.plants["flowers.small"].colour = "${var1}"' -c

The result is:

{"plants":{"flowers.small":{"colour":"${var1}","age":"","vegetables":{"root":"","height":""}}}}

Command:

cat myjson.json|jq --arg v "$var1" '.plants.["flowers.small"].colour = [$v]' -c

The result is:

jq: error: syntax error, unexpected '[', expecting FORMAT or QQSTRING_START (Unix shell quoting issues?) at <top-level>, line 1:
.plants.["flowers.small"].colour = $v
jq: 1 compile error

My jq version is: jq-1.5-1-a5b5cbe

How can I rename a field and set a value for the key from the variables? Is this even doable using the jq version?

2
  • 1
    FYI, it's more efficient to avoid cat in favor of passing the input file name straight to jq or using <myjson.json. Commented Feb 8, 2019 at 23:03
  • I have used cat just for typing facilitation to the pipe when tryping, thank you for the suggestion. For me it does not matter how I can process the json, I just want to have the jq as a component that process the strings passed to be less error prone. Commented Feb 8, 2019 at 23:21

2 Answers 2

1

Using jq as a template engine

If you don't really need input.json to be a separate file, the easy thing to do is to define your entire template as a jq expression:

var1="red"
var2="european.flowers"
var3="european_vegetables"
var4="20"
jq -n --arg var1 "$var1" --arg var2 "$var2" --arg var3 "$var3" --arg var4 "$var4" '
{
 "plants": {
  "\($var2)": {
    "colour": $var1,
    "age": "",
    "\($var3)": {
     "root": "",
     "height": $var4
    }
  }
 }
}'

emits as output:

{
  "plants": {
    "european.flowers": {
      "colour": "red",
      "age": "",
      "european_vegetables": {
        "root": "",
        "height": "20"
      }
    }
  }
}

Alternately: Renaming subtrees

If you really do want to rename an existing key, consider an approach like the below. Using jq variables for the template fields isn't mandatory, but it does make it easier to change your code later:

var1="red"; var2="european.flowers"; var3="european_vegetables"; var4="20"

jq --arg var1 "$var1" --arg var2 "$var2" --arg var3 "$var3" --arg var4 "$var4" '

  # set variables for the fields we expect to see in our input
  "flowers.small" as $plant_tmpl |
  "vegetables" as $cat_tmpl |

  # change things inside fields we will later rename *before* we rename those fields
  .plants[$plant_tmpl].colour = $var1 |
  .plants[$plant_tmpl][$cat_tmpl].height = $var4 |

  if $var3 == $cat_tmpl then . else
    # var3 is not "vegetables" (templated value), so we need to rename it
    .plants[$plant_tmpl][$var3] = .plants[$plant_tmpl][$cat_tmpl] |
    del(.plants[$plant_tmpl][$cat_tmpl])
  end |

  if $var2 == $plant_tmpl then . else
    .plants[$var2] = .plants[$plant_tmpl] |
    del(.plants[$plant_tmpl])
  end
' <<'EOF'
{
 "plants": {
  "flowers.small": {
    "colour": "",
    "age": "",
    "vegetables": {
     "root": "",
     "height": ""
    }
  }
 }
}
EOF
Sign up to request clarification or add additional context in comments.

Comments

1

I suspect this is very close to what you want:

#!/bin/bash

var1="red"
var2="european.flowers"
var3="european_vegetables"
var4="20"

jq --arg var1 "$var1" \
   --arg var2 "$var2" \
   --arg var3 "$var3" \
   --arg var4 "$var4" '
   {"${var1}": $var1, "${var2}": $var2, "${var3}": $var3, "${var4}": $var4} as $dict
   | walk( if type == "string" and $dict[.] then  $dict[.] 
           elif type=="object" then with_entries(if $dict[.key] then .key |= $dict[.] else . end)
           else . end)' template.json

This general approach is OK, but you might want to check the jq Cookbook for suggestions about using jq as a template engine: https://github.com/stedolan/jq/wiki/Cookbook#using-jq-as-a-template-engine

Streamlined version

If your jq supports $ARGS, then you could use this streamlined version of the above:

jq --arg '${var1}' "$var1" \
   --arg '${var2}' "$var2" \
   --arg '${var3}' "$var3" \
   --arg '${var4}' "$var4" '
    $ARGS.named as $dict
    | walk( if type == "string" and $dict[.] then  $dict[.] 
            elif type=="object" then with_entries(if $dict[.key] then .key |= $dict[.] else . end)
            else . end)' template.json

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.