Home > Back-end >  Passing nested json argument variable as key to JQ json library
Passing nested json argument variable as key to JQ json library

Time:10-28

I have two JSON files that I am passing to the jq object, check below:

Create.json

{
    "email_notifications": {
        "on_failure": "_"
    },
    "name": "_",
    "schedule": {
        "quartz_cron_expression": "_",
        "timezone_id": "Europe/Amsterdam",
        "pause_status": "_"
    }
}

Update.json

{
    "job_id": "_",
    "new_settings": {
        "email_notifications": {
            "on_failure": "_"
        },
        "name": "_",
        "schedule": {
            "quartz_cron_expression": "_",
            "timezone_id": "Europe/Amsterdam",
            "pause_status": "_"
        }
}

for Create.json i can use below expression:

TEMPLATE=`cat $FILE | jq
--arg _parent "$PARENT"
--arg _job_name "$JOB_NAME"
' ( . | .name ) |= $_job_name
| ( . | .schedule.pause_status) |= $_parent'
`

for Update.json I need to use the expression to include the parent key name:

TEMPLATE=`cat $FILE | jq
    --arg _parent "$PARENT"
    --arg _job_name "$JOB_NAME"
    ' ( .["new_settings"] | .name ) |= $_job_name
    | ( .["new_settings"] | .schedule.pause_status) |= $_parent'

To handle this I have used if else in the expression and it works perfectly: ( if .job_id? then .["new_settings"] else . end | .name ) |= $_job_name

but I want to pass the initial part as an argument, but it doesn't work and gives a syntax error. How can i make it dynamic that an argument becomes an expression on runtime when passed:

Filter="." OR Filter=".["new_settings"]"
TEMPLATE=`cat $FILE | jq
    --arg filter "$Filter"
    --arg _parent "$PARENT"
    --arg _job_name "$JOB_NAME"
    ' ( $filter | .name ) |= $_job_name
    | ( $filter | .schedule.pause_status) |= $_parent'

CodePudding user response:

# --arg root_path ""
# --arg root_path "new_settings"

getpath( $root_path | split(".") ) |= (
   .name                  = $_job_name |
   .schedule.pause_status = $_parent
)

The argument shouldn't be a piece of jq code. It would be a bad practice to accept a piece of jq code to pass to eval. But jq doesn't even have eval, so it's not even an option.

We could provide a path using a JSON array.

# --argjson root_path '[]'
# --argjson root_path '["new_settings"]'

getpath($root_path)

But a dot-separated path is much nicer.

# --arg root_path ""
# --arg root_path "new_settings"

getpath( $root_path | split(".") )                           # Supports objects
getpath( $root_path | split(".") | map( tonumber? // . ) )   # Supports objects & arrays

This gives us something like this:

getpath( $root_path | split(".") ) as $root |
( $root | .name                  ) |= $_job_name |
( $root | .schedule.pause_status ) |= $_parent

Except there's no point in using $root twice.

getpath( $root_path | split(".") ) as $root |
$root |= (
   .name                  |= $_job_name |
   .schedule.pause_status |= $_parent
)

or just

getpath( $root_path | split(".") ) |= (
   .name                  |= $_job_name |
   .schedule.pause_status |= $_parent
)

The only difference between = and |= is the context (.) provided to the right-hand side. The two previously-existing |= can therefore be simplified to =.

getpath( $root_path | split(".") ) |= (
   .name                  = $_job_name |
   .schedule.pause_status = $_parent
)

Demo on jqplay Create.json
Demo on jqplay Update.json

  • Related