100

I get Cannot iterate over null (null) from the below query because .property_history is not present in result object.

How do i check for the presence of .property_history key before proceeding with map(...) ?

I tried using something like sold_year= `echo "$content" | jq 'if has("property_history") then map(select(.event_name == "Sold"))[0].date' else null end

Original Query:

sold_year=`echo "$content" | jq '.result.property_history | map(select(.event_name == "Sold"))[0].date'`

JSON:

{  
   "result":{  
      "property_history":[  
         {  
            "date":"01/27/2016",
            "price_changed":0,
            "price":899750,
            "event_name":"Listed",
            "sqft":0
         },
         {  
            "date":"12/15/2015",
            "price_changed":0,
            "price":899750,
            "event_name":"Listed",
            "sqft":2357
         },
         {  
            "date":"08/30/2004",
            "price_changed":0,
            "price":739000,
            "event_name":"Sold",
            "sqft":2357
         }
      ]
   }
}
codeforester
  • 39,467
  • 16
  • 112
  • 140
Rahul Dess
  • 2,397
  • 2
  • 21
  • 42

6 Answers6

106

You can use the select-expression in jq to do what you intend to achieve, something as,

jq '.result 
  | select(.property_history != null) 
  | .property_history 
  | map(select(.event_name == "Sold"))[0].date'
Inian
  • 80,270
  • 14
  • 142
  • 161
38

Technically, to test for the presence of a property, you should use has/1, but in the present context, it would probably be better to use the postfix ? operator, e.g.:

$ jq '.result 
  | .property_history[]?
  | select(.event_name == "Sold") 
  | .date'
"08/30/2004"

If there is a possibility that the value of .result is not a JSON object, then you could replace the second line above by:

try(.property_history[])
peak
  • 105,803
  • 17
  • 152
  • 177
28

use has("mykey1") (for objects) or has(0) (for arrays):

jq 'has("name")' <<< '{"name": "hello"}'

output:

true
Ivelin
  • 12,293
  • 5
  • 37
  • 35
123
  • 595
  • 6
  • 18
5

The trick is to use // together with empty:

jq '.result.property_history // empty | map(select(.event_name == "Sold"))[0:1][].date'

Another alternative is to use an additional select:

jq '.result.property_history | select(.) | map(select(.event_name == "Sold"))[0:1][].date'
aff
  • 162
  • 2
  • 6
  • 17
reegnz
  • 837
  • 9
  • 16
5

General pattern:

try (...) // "default_value"

With your logic:

jq 'try (.result.property_history | map(select(.event_name == "Sold"))[0].date) // "default_value"'

try (without a catch) returns empty if the expression fails. // provides a default value if the value is empty.

Curtis Yallop
  • 6,696
  • 3
  • 46
  • 36
2

The shortest you can use (with the bonus that the exit code will be set):

# Example that exits with code 0
jq -e 'has("result")'

# Example that exists with code 0
jq -e '.result[].property_history | has("price")'

# Example that exists with code 1
jq -e '.result[].property_history | has("dummyKey")'

In these examples, because of setting the flag -e, if has returned true, the exit code will be set to 0, otherwise 1. Which saves unnecessary additional steps.

Mohammed Noureldin
  • 14,913
  • 17
  • 70
  • 99