workaround for known jq error while updating fields

Viewed 198

I want to update each value of type "string" in an entire json to a number if possible. If the value cannot be parsed as a number, the string should be kept.

My attempt to solve this with jq runs into a known bug of version 1.6.

I can't figure out how to workaround this bug. Any ideas how to solve the task without running into this known bug?

See also: Update operator yields empty object in JQ

example input

{
  "outer": {
    "inner": [
      {
        "foo": "foo",
        "bar": "11",
        "count": 12,
        "data": ["13", "14"]
      },
      {
        "foo": "foo",
        "bar": "15",
        "count": 16,
        "child": {
          "otherData": ["17", "18"]
        }
      }
    ]
  }
}

desired output

{
  "outer": {
    "inner": [
      {
        "foo": "foo",
        "bar": 11,
        "count": 12,
        "data": [13, 14]
      },
      {
        "foo": "foo",
        "bar": 15,
        "count": 16,
        "child": {
          "otherData": [17, 18]
        }
      }
    ]
  }
}

attempt 1 to solve - not working

jq '(..| strings) |= try tonumber catch .'

output

{
  "outer": {
    "inner": [
      {
        "foo": "Invalid literal at EOF at line 1, column 3 (while parsing 'foo')",
        "bar": {
          "__jq": 1
        },
        "count": 12,
        "data": [
          {
            "__jq": 2
          },
          {
            "__jq": 3
          }
        ]
      },
      {
        "foo": "Invalid literal at EOF at line 1, column 3 (while parsing 'foo')",
        "bar": {
          "__jq": 5
        },
        "count": 16,
        "child": {
          "otherData": [
            {
              "__jq": 6
            },
            {
              "__jq": 7
            }
          ]
        }
      }
    ]
  }
}

attempt 2 to solve - even worse

jq '(..| strings) |= tonumber? // .'

output

{
  "outer": {
    "inner": [
      {
        "count": 12,
        "data": [
          "14"
        ]
      },
      {
        "count": 16,
        "child": {
          "otherData": [
            "18"
          ]
        }
      }
    ]
  }
}

working as expected - but does not solve the task

the bug is related to update operator in combination with try catch expression

jq '(..| strings) |= (. + "___needs_update")'

{
  "outer": {
    "inner": [
      {
        "foo": "foo___needs_update",
        "bar": "11___needs_update",
        "count": 12,
        "data": [
          "13___needs_update",
          "14___needs_update"
        ]
      },
      {
        "foo": "foo___needs_update",
        "bar": "15___needs_update",
        "count": 16,
        "child": {
          "otherData": [
            "17___needs_update",
            "18___needs_update"
          ]
        }
      }
    ]
  }
}
2 Answers

Here's a walk-free solution for jq 1.5 or greater:

reduce paths(strings) as $p (.; 
  getpath($p) as $x | setpath($p; $x | tonumber? // $x))

walk to the rescue:

walk(if type == "string"
     then . as $in | try tonumber catch $in 
     else . end)

or just:

walk(if type == "string" then tonumber? // . else . end)

Enhanced walk

If your jq does not have walk, or if you want an enhanced version relative to the (current) built-in version, see the jq FAQ (search for def walk).

Related