How do you conditionally change a string value to a number in JQ?

Viewed 311

I am pulling a secret from SecretsManager in AWS and using the resulting JSON to build a parameters JSON file that can pass this on to the cloud formation engine. Unfortunately, SecretsManager stores all values as strings, so when I try to pass these values to my cloud formation template it will fail because it is passing a string instead of a number and some cloud formation parameters need to be numbers (e.g. not a string).

In the example below, I want to tell JQ that "HEALTH_CHECK_UNHEALTHY_THRESHOLD_COUNT" and "AUTOSCALING_MAX_CAPACITY" are numbers. So, I prefix the key with "NUMBER::".

This serves two purposes. First, it tells the person viewing this secret that it will be converted to a number, second, it will tell JQ to convert the string value of "2" to 2. This needs to scale so that I can have 1..n keys that need to be converted in the JSON.

Consider this JSON:

{
  "NUMBER::AUTOSCALING_MAX_CAPACITY": "12",
  "SERVICE_PLATFORM_VERSION": "1.3.0",
  "HEALTH_CHECK_PROTOCOL": "HTTPS",
  "NUMBER::HEALTH_CHECK_UNHEALTHY_THRESHOLD_COUNT": "2"
}

Here is what I'd like to do with JQ:

  1. JQ will copy over the key/value pairs for the majority of elements in the JSON "as is". If there is no "NUMBER::" prefix, they are copied over "as is".

  2. However, if a key is prefixed with "NUMBER::" I'd like the following to happen:

    a. JQ will remove the "NUMBER::" prefix from the key name.

    b. JQ will convert the value from a string to a number.

The end result is a JSON that looks like this:

{
  "AUTOSCALING_MAX_CAPACITY": 12,
  "SERVICE_PLATFORM_VERSION": "1.3.0",
  "HEALTH_CHECK_PROTOCOL": "HTTPS",
  "HEALTH_CHECK_UNHEALTHY_THRESHOLD_COUNT": 2
}

What I've tried

I have tried using Map to do this with limited success. In this example I am looking for a specific field mainly as a test. I don't want to have to call out specific keys by name, but rather just use any key that begins with "NUMBER::" to do the conversions.

NOTE: The SECRET_STRING variable in the examples below contains the source JSON.

 echo $SECRET_STRING | jq 'to_entries | map(if .key == "NUMBER::AUTOSCALING_MAX_CAPACITY"  then . + {"value":.value}  else . end ) | from_entries'**

I've also tried to use "tonumber" across the entire JSON. JQ will examine all the values and see if it can convert them to numbers. The problem is it fails when it hits the "SERVICE_PLATFORM_VERSION" key as it detects "1.3.0" as a number and it tries for make that a number, which of course is bogus.

Example: echo $SECRET_STRING | jq -r '.[] | tonumber'

Recap I'd like to use JQ to convert JSON string values to number by use a prefix of "NUMBER::" in the key name.

Note: This problem does not exist when attempting to pull entries from the Systems Manager Parameter Store because AWS allows you use "resolve" entries as strings or numbers. The same feature does not exist in SecretsManager. I'd also like to use the SecretsManager to provide a list of some 30 or more configuration items to set up my stack. With the Parameter store you have to set up each config item as a separate entry, which we be a maintenance nightmare.

1 Answers

Select each entry with a key starting with NUMBER:: and update it to remove that prefix and convert the value to a number.

with_entries(
  select(.key | startswith("NUMBER::")) |= (
    (.key   |= ltrimstr("NUMBER::")) |
    (.value |= tonumber)
  )
)

Online demo

Related