skip to Main Content

I’m looking for a solution to add a new attribute with a JSON object value into an existing JSON file.

My current script:

if [ ! -f "$src_file" ]; then
  echo "Source file $src_file does not exists"
  exit 1
fi
if [ ! -f "$dst_file" ]; then
  echo "Destination file $dst_file does not exists"
  exit 1
fi

if ! jq '.devDependencies' "$src_file" >/dev/null 2>&1; then
  echo "The key "devDependencies" does not exists into source file $src_file"
  exit 1
fi

dev_dependencies=$(jq '.devDependencies' "$src_file" | xargs )

# Extract data from source file
data=$(cat $src_file)

# Add new key-value
data=$(echo $data | jq --arg key "devDependencies" --arg value "$dev_dependencies" '. + {($key): ($value)}')

# Write data into destination file
echo $data > $dst_file

It’s working but the devDependencies value from $dev_dependencies is wrote as string:

"devDependencies": "{ @nrwl/esbuild: 15.6.3, @nrwl/eslint-pl[...]".

How can I write it as raw JSON ?

2

Answers


  1. I think you want the --argjson option instead of --arg. Compare

    $ jq --arg k '{"foo": "bar"}' -n '{x: $k}'
    {
      "x": "{"foo": "bar"}"
    }
    

    with

    $ jq --argjson k '{"foo": "bar"}' -n '{x: $k}'
    {
      "x": {
        "foo": "bar"
      }
    }
    
    Login or Signup to reply.
  2. --arg will create a string variable. Use --argjson to parse the value as JSON (can be object, array or number).

    From the docs:

    --arg name value:

    This option passes a value to the jq program as a predefined variable.
    If you run jq with --arg foo bar, then $foo is available in the
    program and has the value "bar". Note that value will be treated as a
    string, so --arg foo 123 will bind $foo to "123".

    Named arguments are also available to the jq program as $ARGS.named.

    --argjson name JSON-text:

    This option passes a JSON-encoded value to the jq program as a
    predefined variable. If you run jq with --argjson foo 123, then $foo
    is available in the program and has the value 123.

    Note that you don’t need multiple invocations of jq, xargs, command substitution or variables (don’t forget to quote all your variables when expanding).

    To "merge" the contents of two files, read both files with jq and let jq do the work. This avoids all the complications that arise from jumping between jq and shell context. A single line is all that’s needed:

    jq --slurpfile deps "$dep_file" '. + { devDependencies: $deps[0].devDependencies }' "$source_file" > "$dest_file"
    

    or

    jq --slurpfile deps "$dep_file" '. + ($deps[0]|{devDependencies})' "$source_file" > "$dest_file"
    

    alternatively (still a one-liner):

    jq --slurpfile deps "$dev_file" '.devDependencies = $deps[0].devDependencies' "$source_file" > "$dest_file"
    

    peak’s answer here reminded me of the very useful input filter, which can make the program even shorter as it avoids the variable:

    jq '. + (input|{devDependencies})' "$source_file" "$dep_file" > "$dest_file"
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search