11

I'm attempting to generate json output where the input is coming from shell variables.

happystring="Bob Ross"
unhappynumber1="1942"
unhappyboolean=true

JSON=$(jq -n \
        --arg happystring "$happystring" --arg unhappynumber1 "$unhappynumber1" \
        --arg unhappyboolean $unhappyboolean \
        '
        {
            happystring: $happystring,
            unhappynumber1: $unhappynumber1,
            unhappyboolean: $unhappyboolean
        }
        ')

echo "$JSON" | jq

Produces this output:

{
  "happystring": "Bob Ross",
  "unhappynumber1": "1942",
  "unhappyboolean": "true"
}

I know I can use tonumber to convert a string to a number in a simple filter. However, I cannot figure out how to convert a string to a boolean. And I'm having trouble reasoning how to do either when sourcing from shell vars and creating new json as output.

Desired output:

{
  "happystring": "Bob Ross",
  "unhappynumber1": 1942,
  "unhappyboolean": true
}

Would it be easier or more clear if I produced the json and stored it in a shell var in one step, and then performed the additional conversion in a second step?

Balok
  • 133
  • 1
  • 6

2 Answers2

14

There's no need to convert variables to strings like --arg does only to convert them back, you can use --argjson and still input them separately but with types determined by the normal JSON rules:

happystring='"Bob Ross"'
unhappynumber1=1942
unhappyboolean=true

JSON=$(jq -n \
      --argjson happystring "$happystring" \
      --argjson unhappynumber1 "$unhappynumber1" \
      --argjson unhappyboolean "$unhappyboolean" \
      '
      {
          happystring: $happystring,
          unhappynumber1: $unhappynumber1,
          unhappyboolean: $unhappyboolean
      }
      ')

      echo "$JSON"

You could also mix --arg for strings and --argjson for everything else if you prefer not needing literal " quotes for the string arguments, as the note in the manual on shell quoting and the program text really applies to any arguments with characters you need to escape in your shell:

Note: it is important to mind the shell’s quoting rules. As a general rule it’s best to always quote (with single-quote characters) the jq program, as too many characters with special meaning to jq are also shell meta-characters. For example, jq "foo" will fail on most Unix shells because that will be the same as jq foo, which will generally fail because foo is not defined. When using the Windows command shell (cmd.exe) it’s best to use double quotes around your jq program when given on the command-line (instead of the -f program-file option), but then double-quotes in the jq program need backslash escaping.

lossleader
  • 13,182
  • 2
  • 31
  • 45
  • Fantastic! I like the idea of letting the variable types get interpreted the way I intended w/o any additional filters. However, I tested without escaping the `"` in `happystring` and it seems to work fine. Is there a specific reason for using literal `"` in this case? – Balok Feb 22 '19 at 16:41
  • @Balok in my example, I used single quotes to make sure "Bob Ross" was literally quoted when it reached jq, while the quotes in the jq command line are consumed by the shell and are usually used defensively to prevent more dangerous things like interpreting wildcards. Your shell may vary, and the context may vary so problems of what might be consuming quotes is a reason you might still want to use `arg` with some variables. – lossleader Feb 22 '19 at 17:39
9

You can use test filter to do the check and return true/false. And I think you can just use the filters directly, no need to create a json and convert again.

happystring="Bob Ross"
unhappynumber1="1942"
unhappyboolean=true

JSON=$(jq -n \
        --arg happystring "$happystring" --arg unhappynumber1 "$unhappynumber1" \
        --arg unhappyboolean $unhappyboolean \
        '
        {
            happystring: $happystring,
            unhappynumber1: $unhappynumber1 | tonumber,
            unhappyboolean: $unhappyboolean | test("true")
        }
        ')

echo "$JSON" | jq

will give output as:

{
  "happystring": "Bob Ross",
  "unhappynumber1": 1942,
  "unhappyboolean": true
}
Kamal
  • 2,384
  • 1
  • 13
  • 25
  • I had multiple use-cases, some of which involved passing in the arguments as $1, $2, etc. I found this solution works well for both cases, explicitly setting variables and passing in arguments, so I have switched the accepted answer to this one. – Balok Feb 22 '19 at 17:35