A tricky difference between JSON(Path) and XML(Path)
Developers and testers familiar with XPath may become frustrated when trying to make more complex JSONPath expressions. Also, sometimes people get surprised that their JSONPath was working some time ago but suddenly stopped.
XML and JSON are tree-like structures, so querying should be very similar. Even more, JSONPath is usually considered to be the XPath for JSON. So, what's the difference?
Selecting nodes in XPath
First, let's try to find mobile phone contacts in the following XML with XPath:
<?xml version="1.0" encoding="UTF-8"?
<purchases>
<customer>
<firstName>John</firstName>
<lastName>Brown</lastName>
<age>26</age>
<address>
<streetAddress>Tibera street 23</streetAddress>
<city>Rome</city>
<postalCode>12365</postalCode>
</address>
<phone>
<type>home</type>
<number>0123-4567-8888</number>
</phone>
</customer>
<customer>
<firstName>Anne</firstName>
<lastName>Fox</lastName>
<age>25</age>
<address>
<streetAddress>Flower street 206</streetAddress>
<city>Glasgow</city>
<postalCode>98653</postalCode>
</address>
<phone>
<type>mobile</type>
<number>+41-569-9563</number>
</phone>
</customer>
</purchases>>
Using this XPath expression, we get all phone contacts:
//customer/phone
If we decide to get the mobile phones only, we apply the condition:
//customer/phone[type='mobile']
Trying the same with JSONPath.
Now, let's move on to JSON and JSONPath:
{
"customers": [
{
"firstName": "John",
"lastName": "Brown",
"age": 26,
"address": {
"streetAddress": "Tibera street 23",
"city": "Rome",
"postalCode": "12365"
},
"phone": {
"type": "home",
"number": "0123-4567-8888"
}
},
{
"firstName": "Anne",
"lastName": "Fox",
"age": 25,
"address": {
"streetAddress": "Flower street 206",
"city": "Glasgow",
"postalCode": "98653"
},
"phone": {
"type": "mobile",
"number": "+41-569-9563"
}
}
]
}
JSONPath syntax is a bit different but getting all phones is still pretty straightforward:
$..phone
Now, how to pick the mobile phones only. Any guess? Applying a condition? OK, let's give it a try:
Recommended by LinkedIn
$..phone[?(@.type=='mobile')]
Does that work? Yes, it works! No, it does NOT! Give it a try in online JSOPath tools, e.g., https://jsonpath.com or https://jsonpath.herokuapp.com/. If you check the second app, you will find that the JSONPath expression works in Jayway but not in the Goessner implementation (see screenshot below).
What's wrong with my expression? The syntax seems all right. The issue is that JSONPath treats the square brackets [] operator differently than XPath. In XPath, you could apply the condition on any XML node. However, in JSONPath, you can apply the [] operator only ARRAYS. In our example, we have an array customers:
$..customers[?(@.phone.type=='mobile')].phone
What does that mean:
Try the expression above in JSONPath evaluators. It should work fine.
Conclusion
In complex JSON documents, we need to put lengthy condition inside [] (path + condition) and then repeat part of it (path) just behind the brackets.
Some JSONPath implementations followed XPath and allowed to use the [] operator with a condition on any JSON element. However, the array-only usage took over.
I assume the rationale is that JSON contains arrays; thus, JSONPath needs an array operator to select a specific item. On the other hand, XML does not distinguish between arrays and other nodes and allows repeating the elements of the same type without making an array.
JSONPath follows many XPath principles, allowing the selection anywhere would be more intuitive. Maybe a lesson learned for future designs: if you apply a well-known pattern, use it as much as possible, without unnecessary modifications.
Discussion
You are welcome to share your opinions and experience in the discussion. Also, if you find anything which needs to be fixed, let me know.
Karel@apimate.eu, https://apimate.eu
$.customers..[?(@.age > 25)].firstName very nice just like selenium 4 relativelocators