JSONPath-syntaxisgids: JSON opvragen en filteren met voorbeelden
JSONPath is een querytaal voor JSON, net zoals XPath een querytaal is voor XML. Je schrijft een padexpressie en de evaluator geeft elke waarde terug die matcht. Wil je alle auteursnamen uit een boekwinkeldocument halen, dan schrijf je $.store.book[*].author — en je krijgt de lijst met auteurs terug, zonder ook maar één regel doorlopcode.
Deze gids loopt elke selector in de JSONPath-syntaxis langs met kant-en-klare voorbeelden die je al lezend kunt uitvoeren. Er zijn twee dialecten, en dat is meteen het belangrijkste om vooraf te weten. Het Goessner-dialect uit 2007 is de facto de klassieke variant, en RFC 9535 is de formele IETF-standaard die in februari 2024 is gepubliceerd. Ze zijn het eens over de gewone paden en wijken af in de randgevallen, dus deze gids markeert de verschillen zodra ze opduiken. Je kunt elke expressie hieronder uitproberen in de JSONPath Tester en tussen beide engines schakelen om te vergelijken.
Hier is om te beginnen het selector-spiekbriefje. De rest van het artikel werkt elke rij uit met een uitgewerkt voorbeeld tegen één gedeeld JSON-document.
| Selector | Betekenis | Voorbeeld |
|---|---|---|
$ | Root van het document | $ |
@ | Huidig element (in filters) | [?(@.price < 10)] |
.name / ['name'] | Onderliggend lid | $.store.book |
.. | Recursieve afdaling | $..author |
* | Alle elementen / leden | $.store.book[*] |
[0] | Array-index | $.store.book[0] |
[start:end:step] | Array-slice (halfopen) | $.store.book[0:2] |
[a,b] | Unie van namen / indices | $.store.book[0,2] |
[?()] | Filterexpressie | $.store.book[?(@.price < 10)] |
length() count() match() search() value() | RFC 9535-functies (alleen in filters) | [?length(@.title) > 15] |
Wat is JSONPath?
JSONPath is een declaratieve querytaal om knopen uit een JSON-document te selecteren. In plaats van een lus te schrijven die objecten en arrays doorloopt, beschrijf je met een pad de locatie die je wilt, en de evaluator geeft de matchende waarden terug. Het mentale model is hetzelfde dat XPath je voor XML geeft: een pad van selectoren die door de structuur heen stappen.
Het duikt overal op waar ontwikkelaars met JSON werken. Je gebruikt het om een veld uit een API-respons te plukken, om een waarde te asserteren in een integratietest, om velden te adresseren in pipelineconfiguraties voor Kubernetes, AWS Step Functions en Azure Logic Apps, en om data te extraheren uit grote of onregelmatige JSON zonder zelf doorlooplogica te schrijven.
Een kort woordje geschiedenis, want het verklaart de splitsing in dialecten. Stefan Goessner stelde JSONPath in 2007 voor. Het verspreidde zich snel en werd een de-factostandaard, maar het is nooit formeel gespecificeerd — dus implementaties dreven uiteen op de details. De IETF dichtte dat gat in februari 2024 met RFC 9535, de eerste formele JSONPath-specificatie. Beide dialecten leven vandaag voort, en daarom kan dezelfde expressie zich anders gedragen afhankelijk van welke bibliotheek hem uitvoert.
Voordat je begint met opvragen, helpt het om de structuur te lezen. Print rommelige invoer netjes uit met de JSON Formatter zodat de nesting zichtbaar wordt.
Het voorbeelddocument
Elk voorbeeld hieronder draait tegen de klassieke Goessner-boekwinkel-JSON. Plak dit één keer en hergebruik het:
{
"store": {
"book": [
{ "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
{ "title": "Sword of Honour", "author": "Evelyn Waugh", "price": 12.99 },
{ "title": "Moby Dick", "author": "Herman Melville", "price": 8.99 },
{ "title": "The Lord of the Rings", "author": "J. R. R. Tolkien", "price": 22.99 }
],
"bicycle": { "color": "red", "price": 19.95 }
}
}
Vier boeken met een titel, auteur en prijs, plus een fiets. Hou dit in gedachten: de prijzen zijn 8.95, 12.99, 8.99 en 22.99, en die bepalen straks de filterresultaten.
Root, child en recursieve afdaling ($ . ..)
Elke expressie begint bij de root, geschreven als $. Van daaruit stap je naar onderliggende leden met een punt of met bracketnotatie — de twee zijn gelijkwaardig:
$.store.book → the book array
$['store']['book'] → identical result
Bracketnotatie heb je nodig wanneer een sleutel spaties, punten of andere speciale tekens bevat: $['first name'] werkt waar $.first name dat niet zou doen.
De operator .. is recursieve afdaling. Hij doorzoekt elk niveau van het document, niet alleen de directe kinderen, en verzamelt alles wat matcht met de volgende selector op willekeurige diepte:
$..author
→ ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]
Wanneer gebruik je .. versus een volledig pad
Recursieve afdaling is handig maar bot. $..price matcht elke prijs waar dan ook in de boom — inclusief store.bicycle.price, die je misschien niet wilde. Wanneer je de vorm kent, schrijf je het pad voluit zodat de query precies blijft:
$..price → [8.95, 12.99, 8.99, 22.99, 19.95] (includes the bicycle)
$.store.book[*].price → [8.95, 12.99, 8.99, 22.99] (only books)
Bewaar .. voor echt onregelmatige of onbekende structuren. De afweging is gemak tegen controle: hoe meer je over je data weet, hoe meer je de voorkeur moet geven aan een expliciet pad.
Wildcards, indices en array-slices (* [0] [start:end:step])
De wildcard * selecteert alle elementen van een array of alle leden van een object:
$.store.book[*].title
→ ["Sayings of the Century", "Sword of Honour", "Moby Dick", "The Lord of the Rings"]
Array-indices zijn nul-gebaseerd, en negatieve indices tellen vanaf het eind:
$.store.book[0].title → ["Sayings of the Century"]
$.store.book[-1].title → ["The Lord of the Rings"]
Slices gebruiken dezelfde halfopen [start:end:step]-conventie als Python en JavaScript: start is inbegrepen, end is uitgesloten.
$.store.book[0:2].title → ["Sayings of the Century", "Sword of Honour"]
Dat geeft twee boeken terug, geen drie: index 0 en index 1, stoppend vóór index 2. De uitsluitende eindgrens is de allerbekendste JSONPath-bug, dus het loont om hem goed te onthouden:
[0:2] → first TWO elements (indices 0, 1) ← correct
[0:3] → first THREE elements (indices 0, 1, 2)
Laat een grens weg om tot het uiteinde door te lopen, en voeg een step toe voor elk N-de element:
$.store.book[2:].title → ["Moby Dick", "The Lord of the Rings"]
$.store.book[:3].title → first three titles
$.store.book[::2].title → ["Sayings of the Century", "Moby Dick"] (every other)
Filterexpressies [?()] — het krachtige deel
Filters zijn waar JSONPath zijn waarde bewijst. Een filter [?()] houdt alleen de elementen over waarvoor een predicaat waar is, en binnen het filter verwijst @ naar het huidige element dat wordt getest.
Om boeken te selecteren die goedkoper zijn dan 10:
$.store.book[?(@.price < 10)].title
→ ["Sayings of the Century", "Moby Dick"]
Tegen de boekwinkelprijzen (8.95, 12.99, 8.99, 22.99) kwalificeren twee boeken. Zo bouw je filterpredicaten stap voor stap op:
- Vergelijk met een literal. Gebruik
==,!=,<,<=,>,>=, bijvoorbeeld@.price > 10. - Match een string. Stringliterals nemen enkele aanhalingstekens:
@.author == 'Nigel Rees'. - Test op bestaan. Een kale verwijzing naar een lid selecteert elementen die het hebben:
[?(@.isbn)]houdt alleen boeken met eenisbn. - Combineer voorwaarden. Verbind predicaten met
&&en||:[?(@.price < 10 && @.author == 'Herman Melville')].
De meest voorkomende filterfout is de scope van @. Binnen het predicaat is het huidige element @, niet $. Schrijf je $.price, dan wijst dat terug naar de root van het document, niet naar het boek dat wordt getest:
$.store.book[?($.price < 10)] → wrong scope, matches nothing useful
$.store.book[?(@.price < 10)] → correct: each book's own price
RFC 9535 versus Classic in filters
De twee dialecten gaan uiteen bij witruimte en aanhalingstekens. Classic is soepel: [?(@.price<10)] zonder spaties wordt prima ingelezen. RFC 9535 volgt zijn grammatica exact en is strenger over hoe het filter is geschreven. Als een filter dat elders werkte hier faalt, controleer dan de spatiëring en de engine. Hou filters schoon (operatoren met spaties, strings met enkele aanhalingstekens) en ze evalueren hetzelfde, ongeacht welke bibliotheek ze uiteindelijk uitvoert.
Unieselectoren — meerdere sleutels in één keer selecteren ([a,b])
Een unieselector noemt meerdere namen of indices binnen één bracket en verzamelt ze allemaal:
$.store.book[0]['title','author']
→ ["Sayings of the Century", "Nigel Rees"]
Unies werken ook met indices, en je kunt ze mengen met andere selectoren voor een vaste projectie:
$.store.book[0,2].title → ["Sayings of the Century", "Moby Dick"]
$.store.book[*]['title','price'] → title and price of every book
Unies zijn het juiste gereedschap wanneer je een paar specifieke velden wilt in plaats van een heel object of een wildcardveeg.
RFC 9535-functies: length, count, match, search, value
RFC 9535 definieert vijf standaard functie-extensies. Eén regel laat bijna iedereen struikelen, en concurrerende gidsen hebben hem vaak verkeerd:
Deze functies zijn alleen aanroepbaar binnen een filter
[?...], nooit als losstaand padsegment.
$.store.book.length() schrijven is geen geldige RFC 9535, en de standaardgrammatica verwerpt het. Die segment-aanroepvorm is een jsonpath-plus-extensie, geen onderdeel van de spec. Om op lengte te filteren, roep je de functie aan binnen het predicaat:
$.store.book[?length(@.title) > 15]
→ [
{ "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
{ "title": "The Lord of the Rings", "author": "J. R. R. Tolkien", "price": 22.99 }
]
Beide geselecteerde titels zijn langer dan 15 tekens; “Moby Dick” (9) en “Sword of Honour” (precies 15, dus niet meer dan 15) vallen af.
Dit doet elke functie binnen een filter:
length()— lengte van een string, array of object:[?length(@.title) > 15]count()— aantal knopen in een nodelist:[?(count(@.authors) > 1)]match()— regex-test op de hele string (I-Regexp-patroon):[?match(@.author, 'J.*')]search()— regex-test op een substring:[?search(@.title, 'the')]value()— zet een nodelist met één knoop om naar zijn waarde voor vergelijking
Alle vijf zijn een RFC 9535-feature. Het klassieke (Goessner-)dialect implementeert ze niet, dus als een op functies gebaseerde expressie faalt, bevestig dan dat je hem binnen een filter aanroept en dat je engine op RFC 9535 staat.
RFC 9535 versus Classic Goessner — waarom dezelfde expressie verschilt
Wanneer een JSONPath-expressie verschillende resultaten geeft in twee tools, is het dialect meestal de reden. Zo verhouden de twee zich:
| Aspect | Classic Goessner (2007) | RFC 9535 (2024) |
|---|---|---|
| Standaardisatie | De facto, nooit geformaliseerd | Eerste formele IETF-specificatie |
| Witruimte/aanhalingstekens in filter | Soepel ([?(@.price<10)] OK) | Strikt, volgt grammatica exact |
| Vergelijking met ontbrekend lid | Implementatieafhankelijk | Welgedefinieerd, gooit geen fout |
| Standaardfuncties | Geen onderdeel van het dialect | length count match search value |
| Genormaliseerde paden | Geen canonieke vorm | Canoniek, bracketvorm met enkele aanhalingstekens |
| Volgorde van unie | Verschilt per bibliotheek | Gespecificeerd |
Praktisch advies: als je downstream-systeem RFC 9535-conformiteit adverteert, schrijf en valideer dan tegen de standaardengine. Onderhoud je een expressie die je van jsonpath.com, jsonpath-plus of een Jayway-gebaseerde dienst hebt gekopieerd, gebruik dan Classic zodat resultaten reproduceerbaar zijn. De JSONPath Tester draait beide engines achter één schakelaar, dus je kunt een expressie één keer plakken en naast elkaar zien hoe elk dialect ermee omgaat. Die vergelijking met dubbele engine wijst een afwijking meestal snel aan.
JSONPath versus XPath versus jq — welke gebruik je
Deze drie worden door elkaar gehaald, dus hier is de korte versie:
- JSONPath is een declaratieve padquery voor JSON. Hij komt het best tot zijn recht ingebed in config- en testasserties waar je een locatie wilt benoemen zonder code te schrijven.
- XPath is de tegenhanger uit de XML-wereld. JSONPath leende er wat notatie van (
*,..,[]), en daarom houdt de analogie stand, maar de talen zijn niet uitwisselbaar en de functiesets verschillen. - jq is een JSON-processor voor de commandoregel. Hij gaat veel verder dan padselectie, met transformatie, aggregatie en herschikking, en leeft in je shell-pipeline.
De keuze is meestal helder. Voor een ingebedde assertie of een veld in een pipelineconfig grijp je naar JSONPath. Voor shell-gedreven transformatie en datasleutelwerk grijp je naar jq; de jq-spiekbrief behandelt die workflow grondig. En wanneer de vraag is of een payload aan een verwachte vorm voldoet in plaats van waar een veld zit, valideer je hem met de JSON Schema Validator en de bijbehorende complete validatiegids.
7 veelgemaakte JSONPath-fouten
- De root
$vergeten.store.bookwordt door de meeste engines afgewezen; elke expressie begint bij$. - Slice net ernaast.
[0:2]is twee elementen, geen drie — de eindgrens is uitsluitend. - Verkeerd dialect. Een Classic-expressie onder RFC 9535 draaien (of omgekeerd) kan een parsefout geven of andere knopen matchen. Zet de engine goed.
- Functie als losstaand segment.
$.store.book.length()is ongeldig RFC 9535; roeplength()binnen een filter aan. @in een filter vergeten.[?($.price < 10)]wijst naar de root; gebruik[?(@.price < 10)].- Verkeerde bracketquoting.
$[store]is een fout; zet de sleutel tussen aanhalingstekens:$['store']. - Aangenomen dat
..bij het eerste niveau stopt. Recursieve afdaling matcht op elke diepte, niet alleen directe kinderen.
Test JSONPath online, privé
De snelste manier om de JSONPath-syntaxis te leren, is hem uit te voeren. De JSONPath Tester evalueert elke expressie in deze gids live: dubbele engines voor RFC 9535 en Classic, resultaatweergaven Values / Paths / Both, genormaliseerde paden voor debuggen, en uitvoering volledig in de browser. Er gaat niets naar een server, je hoeft niet in te loggen, en er komt geen eval aan te pas, dus vertrouwelijke payloads blijven veilig. Bouw hier een pad, bevestig dat het precies de knopen selecteert die je wilt, en plak de gevalideerde expressie daarna rechtstreeks in je code, tests of pipeline.
Voor de rest van de JSON-workflow: zet een voorbeeldrespons om in getypeerde interfaces met JSON to TypeScript, of vergelijk twee documenten veld voor veld met JSON Diff.
Veelgestelde vragen
Waarvoor wordt JSONPath gebruikt?
JSONPath bevraagt JSON zonder imperatieve code. Ontwikkelaars gebruiken het om velden uit API-responsen te plukken, waarden te asserteren in integratietests, en velden te adresseren in configuraties voor Kubernetes, AWS Step Functions en Azure Logic Apps. Het blinkt uit in het extraheren van data uit grote of onregelmatige structuren waar een doorloop met de hand schrijven omslachtig zou zijn.
Wat is het verschil tussen RFC 9535 en klassieke JSONPath?
Classic is Stefan Goessners de-factodialect uit 2007 — breed geïmplementeerd maar nooit formeel gespecificeerd, dus bibliotheken dreven uiteen. RFC 9535 is de formele specificatie van de IETF uit februari 2024: hij definieert een precieze grammatica, genormaliseerde paden voor resultaten, en vijf standaardfuncties. De twee verschillen aan de randen bij filters, unies en vergelijking met ontbrekende leden.
Hoe werken JSONPath-filterexpressies?
Een filter [?()] houdt alleen de elementen over waarvan het predicaat waar is, en @ is het huidige element. Bijvoorbeeld $.store.book[?(@.price < 10)] selecteert boeken met een prijs onder 10. Je kunt voorwaarden combineren met && en ||, testen of een lid bestaat, en vergelijken met string- of getalliterals.
Kan ik length() als $.store.book.length() gebruiken?
Nee. In RFC 9535 zijn length() en de andere vier functies alleen aanroepbaar binnen een filter, zoals $.store.book[?length(@.title) > 15]. De losstaande segmentvorm $.store.book.length() is een jsonpath-plus-extensie, geen standaard JSONPath, en de RFC 9535-grammatica verwerpt hem.
Is JSONPath hetzelfde als XPath?
Nee, maar het idee is vergelijkbaar. XPath bevraagt XML; JSONPath bevraagt JSON; beide lokaliseren knopen met padselectoren. JSONPath leende doelbewust wat XPath-notatie — *, .. en [] — wat de analogie nuttig maakt, maar de syntaxis, semantiek en functiesets zijn verschillend en niet uitwisselbaar.
Wat doet recursieve afdaling (..) in JSONPath?
De operator .. doorzoekt elk niveau van het document, niet alleen de directe kinderen. $..author verzamelt elk author-lid waar het ook voorkomt, op elke nestingdiepte. Het is een handige manier om één veld uit een diep geneste of onregelmatige structuur te plukken, maar het kan veel meer knopen matchen dan je verwacht, dus beperk het waar je kunt.