jq – How to Set a Value Conditionally in JSON

jqjson

I need to set the value of a field in a json doc to one of three values using jq, depending on which exists. Notionally this looks like: set X to (if A exists, else if B exists, else if C exists, else "").

An example json doc I have looks like this:

{
  "name": "0230",
  "publish_date": "2007-08-18",
  "abc_severity": "",
  "def_severity": "medium",
  "ghi_severity": "negligible"
}

I would like to create a field Severity and set it's value to the value of abc_severity not null or empty. if it is null or empty I would like to set it to def_severity, and if that's null or empty I would like to set it to ghi_severity. If all three are null or empty is may be created with an empty value "". So the output in this case would be:

{
  "name": "0230",
  "publish_date": "2007-08-18",
  "abc_severity": "",
  "def_severity": "medium",
  "ghi_severity": "negligible",
  "Severity": "medium"
}

The following is the closest I seem to be able to get:

'. | if .abc_severity? then .Severity=.abc_severity else if .def_severity? then .Severity=.def_severity else if .ghi_severity? then .Severity=.ghi_severity else .Severity="" end end end'

But the value of Severity is always "" even if one or more of the other values exist. I'm sure I'm overlooking something simple here, I just can't seem to get it.

Best Answer

An empty string is still a string, so .abc_severity? would give you an empty string, not null (or false). Also, note that the question mark means approximately "replace with null if this key does not exist". In the example, all three keys exist, and their values are not null.

If you had been using null for empty values, your jq expression would have looked like

.Severity = (.abc_severity // .def_severity // .ghi_severity )

The above expression would have picked the first of the three values that weren't null, testing the left-most first and going right, or null if all of them were null. But that won't work now as we have to deal with empty strings as if they were null.

We can do that by introducing a helper function (to reduce our typing a bit):

def n: if . == "" then null else . end;
.Severity = ((.abc_severity|n) // (.def_severity|n) // (.ghi_severity|n) // "")

Our helper function n returns the string as-is if it is not empty; otherwise, it returns null. With chained // operators, we pick the first value of the three that is not null, when seen through n, or the empty string if all three values are null.

Testing the above on the command line with your data:

$ jq 'def n: if . == "" then null else . end; .Severity = ((.abc_severity|n) // (.def_severity|n) // (.ghi_severity|n) // "")' file
{
  "name": "0230",
  "publish_date": "2007-08-18",
  "abc_severity": "",
  "def_severity": "medium",
  "ghi_severity": "negligible",
  "Severity": "medium"
}
Related Question