Hvis du nogensinde har stirret på et dybt indlejret API-svar og tænkt "jeg har bare brug for det ene felt derinde" — er JSONPath det værktøj, du leder efter. Det er et forespørgselssprog til JSON, åndeligt lidt ligesom XPath fungerer for XML. Du skriver et stiudtryk, og du får de matchende værdier tilbage. Ingen løkker, ingen manuel traversering. Stefan Goessner introducerede det i 2007, og efter år med lidt inkompatible implementationer, der flød rundt, blev det formelt standardiseret som RFC 9535 i 2024. Du kan også udforske JSONPath-udtryk interaktivt med JSON Path-værktøjet.
Det datasæt, vi vil bruge gennem hele artiklen
Frem for abstrakte eksempler, lad os arbejde med noget realistisk — et e-handelsordresvar. Det er den slags JSON, du ville få tilbage fra en ordrestyringssystem-API:
{
"order": {
"id": "ORD-9182",
"status": "shipped",
"customer": {
"id": "CUST-441",
"name": "Maria Chen",
"email": "[email protected]"
},
"shippingAddress": {
"street": "14 Maple Avenue",
"city": "Portland",
"state": "OR",
"zip": "97201",
"country": "US"
},
"lineItems": [
{
"sku": "WH-1042",
"name": "Wireless Headphones",
"qty": 1,
"unitPrice": 89.99,
"inStock": true
},
{
"sku": "CB-USB-C",
"name": "USB-C Charging Cable",
"qty": 2,
"unitPrice": 12.49,
"inStock": true
},
{
"sku": "SC-PRO-7",
"name": "Phone Stand Pro",
"qty": 1,
"unitPrice": 34.00,
"inStock": false
}
],
"totals": {
"subtotal": 148.97,
"shipping": 5.99,
"tax": 11.92,
"total": 166.88
}
}
}Rodoperatoren $ og grundlæggende navigation
Hvert JSONPath-udtryk starter med $, som refererer til dokumentets rod.
Derfra navigerer du med punktummer. Vil du have ordrestatus? Det er ligetil:
$.order.status
// → "shipped"
$.order.customer.name
// → "Maria Chen"
$.order.totals.total
// → 166.88
$.order.shippingAddress.city
// → "Portland"Du kan også bruge klammernotation, som er nyttig, når en nøgle indeholder mellemrum eller specialtegn, eller når du tilgår arrayelementer via indeks. Begge stilarter er gyldige ifølge RFC 9535's syntaksregler:
// Punktnotation
$.order.customer.email
// Klammernotation — ækvivalent
$['order']['customer']['email']
// Arrayindeks — første linjepost
$.order.lineItems[0].name
// → "Wireless Headphones"
// Sidste linjepost (negativt indeksering — understøttet i RFC 9535)
$.order.lineItems[-1].name
// → "Phone Stand Pro"$[-1:] (slice-syntax) og
$.array[-1] (direkte negativt indeks) er begge gyldige i RFC 9535, men nogle ældre
implementationer understøtter dem ikke. Test mod dit specifikke bibliotek, hvis du bruger negative indekser.Jokertegn — forespørg alle børn på én gang
Jokertegnet * matcher alle elementer på et givet niveau. I stedet for at spørge om
lineItems[0], lineItems[1] osv., kan du hente alt på én gang:
// Alle linjepostnavne
$.order.lineItems[*].name
// → ["Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]
// Alle linjeposters enhedspriser
$.order.lineItems[*].unitPrice
// → [89.99, 12.49, 34.00]
// Alle linjeposters SKU-koder
$.order.lineItems[*].sku
// → ["WH-1042", "CB-USB-C", "SC-PRO-7"]Dette er det mønster, du vil bruge oftest, når du arbejder med arrays i API-svar. I stedet for at mappe over et array i din applikationskode kan du udtrække præcis det felt, du har brug for, før det overhovedet når din forretningslogik.
Rekursiv søgning — find værdier på alle dybder
Operatoren .. udfører en rekursiv søgning gennem hele dokumenttræet.
Det er som en dybde-først-gennemgang, der samler alle noder, der matcher nøglenavnet, uanset hvor de befinder sig:
// Find hvert "name"-felt et vilkårligt sted i dokumentet
$..name
// → ["Maria Chen", "Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]
// Find hvert "city"-felt et vilkårligt sted
$..city
// → ["Portland"]
// Find hvert felt kaldet "id" på enhver dybde
$..id
// → ["ORD-9182", "CUST-441"]Læg mærke til, hvordan $..name finder både kundenavnet og alle produktnavne —
det er ligegyldigt med dybden. Det er kraftfuldt til schemaudforskning: når du har fået et
ukendt JSON-klump og vil finde alle værdier for en bestemt nøgle uden at kende strukturen.
Hvis du vil tjekke selve JSON-filen først, er JSON Validator
og JSON Formatter praktiske startpunkter.
Array-slicing
JSONPath låner Python-stilens slice-notation for arrays. Formatet er
[start:end:step], hvor enhver del kan udelades.
Dette er dokumenteret i RFC 9535 § 2.3.5:
// Første to linjeposterne
$.order.lineItems[0:2]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]
// Fra indeks 1 til slutningen
$.order.lineItems[1:]
// → [{ sku: "CB-USB-C", ... }, { sku: "SC-PRO-7", ... }]
// Kun det sidste element (slice-syntax — bredt understøttet)
$.order.lineItems[-1:]
// → [{ sku: "SC-PRO-7", ... }]
// Hvert andet element (trin på 2)
$.order.lineItems[0::2]
// → [{ sku: "WH-1042", ... }, { sku: "SC-PRO-7", ... }]Filterudtryk — forespørgsel efter betingelse
Det er her JSONPath virkelig viser sin værdi. Filtersyntaksen [?(...)]
lader dig forespørge arrayelementer efter deres feltværdier. Symbolet @ refererer til det
aktuelle element, der testes. Stefan Goessners
originale artikel fra 2007
introducerede denne syntax, og RFC 9535 formaliserede den:
// Linjeposterne under $50
$.order.lineItems[?(@.unitPrice < 50)]
// → [{ sku: "CB-USB-C", unitPrice: 12.49, ... }, { sku: "SC-PRO-7", unitPrice: 34.00, ... }]
// Kun varer der er på lager
$.order.lineItems[?(@.inStock == true)]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]
// Varer hvor qty er større end 1
$.order.lineItems[?(@.qty > 1)]
// → [{ sku: "CB-USB-C", qty: 2, ... }]
// Varer der IKKE er på lager
$.order.lineItems[?(@.inStock == false)].name
// → ["Phone Stand Pro"]Du kan også kombinere betingelser. De fleste implementationer understøtter && og ||
inde i filterudtryk, hvilket lader dig skrive ting som
[?(@.inStock == true && @.unitPrice < 30)].
Tjek din biblioteksdokumentation — adfærd omkring operatørunderstøttelse var en af de vigtigste inkonsekvenser
mellem pre-RFC-implementationer, som
RFC 9535-standardiseringen sigtede mod at løse.
[?(@.fieldName)]. Hvis for eksempel
nogle linjeposterne havde et discountCode-felt og andre ikke,
ville $.order.lineItems[?(@.discountCode)] kun returnere de rabatterede.Brug af JSONPath i JavaScript
Der er endnu ingen indbygget JSONPath-understøttelse i JavaScript (i modsætning til XPath, som har
document.evaluate() i browseren). Du har brug for et bibliotek.
De to mest populære er jsonpath
og jsonpath-plus.
Pakken jsonpath-plus vedligeholdes mere aktivt og har bedre RFC 9535-tilpasning:
npm install jsonpath-plusimport { JSONPath } from 'jsonpath-plus';
const order = {
order: {
id: 'ORD-9182',
status: 'shipped',
customer: { name: 'Maria Chen', email: '[email protected]' },
lineItems: [
{ sku: 'WH-1042', name: 'Wireless Headphones', qty: 1, unitPrice: 89.99, inStock: true },
{ sku: 'CB-USB-C', name: 'USB-C Charging Cable', qty: 2, unitPrice: 12.49, inStock: true },
{ sku: 'SC-PRO-7', name: 'Phone Stand Pro', qty: 1, unitPrice: 34.00, inStock: false }
],
totals: { subtotal: 148.97, shipping: 5.99, tax: 11.92, total: 166.88 }
}
};
// Get all product names
const names = JSONPath({ path: '$.order.lineItems[*].name', json: order });
console.log(names);
// [ 'Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro' ]
// Get in-stock items under $50
const affordable = JSONPath({ path: '$.order.lineItems[?(@.inStock == true && @.unitPrice < 50)]', json: order });
console.log(affordable.map(item => item.name));
// [ 'USB-C Charging Cable' ]
// Get the order total
const total = JSONPath({ path: '$.order.totals.total', json: order });
console.log(total[0]); // 166.88Bemærk at JSONPath() altid returnerer et array — selv når du forespørger en enkelt værdi.
Det er designet sådan: et stiudtryk er en selektor, og en selektor kan matche nul, ét eller mange noder.
Så tag altid result[0], når du ved, at du sigter mod en unik værdi.
Brug af JSONPath i Python
I Python er biblioteket
jsonpath-ng
den mest komplette mulighed. Det understøtter kernespezifikationen plus det meste af filterudtrykssyntaksen:
pip install jsonpath-ngfrom jsonpath_ng import parse
order = {
"order": {
"id": "ORD-9182",
"status": "shipped",
"customer": {"name": "Maria Chen", "email": "[email protected]"},
"lineItems": [
{"sku": "WH-1042", "name": "Wireless Headphones", "qty": 1, "unitPrice": 89.99, "inStock": True},
{"sku": "CB-USB-C", "name": "USB-C Charging Cable", "qty": 2, "unitPrice": 12.49, "inStock": True},
{"sku": "SC-PRO-7", "name": "Phone Stand Pro", "qty": 1, "unitPrice": 34.00, "inStock": False}
],
"totals": {"subtotal": 148.97, "shipping": 5.99, "tax": 11.92, "total": 166.88}
}
}
# Parse the expression once, then reuse it (more efficient)
expr = parse("$.order.lineItems[*].name")
names = [match.value for match in expr.find(order)]
print(names)
# ['Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro']
# Get all line items and check stock
expr2 = parse("$.order.lineItems[*]")
for item in [m.value for m in expr2.find(order)]:
status = "in stock" if item["inStock"] else "OUT OF STOCK"
print(f"{item['name']} (${item['unitPrice']}) — {status}")
# Output:
# Wireless Headphones ($89.99) — in stock
# USB-C Charging Cable ($12.49) — in stock
# Phone Stand Pro ($34.0) — OUT OF STOCKMønstret parse() + find() er den rigtige tilgang til produktionsbrug —
kompiler udtrycket én gang og kør det mod mange dokumenter, snarere end at gen-parse stisstrengen hver gang.
jsonpath-ng-dokumentationen på GitHub
har mere detalje om udvidet filtersyntaks og metoden update() til at ændre matchede noder.
JSONPath vs jq — hvilket skal du vælge?
Folk sammenblander nogle gange JSONPath og jq — begge "forespørger JSON", men de løser forskellige problemer. Her er den praktiske opdeling:
- JSONPath er et stiudtrykssprog designet til at blive indlejret i applikationer. Det forespørger og uddrager værdier. Du bruger det inde i JavaScript-, Python- eller Java-kode, eller konfigurationsfiler som Kubernetes-selektorer.
- jq er en fuld kommandolinjeprocessor med sit eget programmeringssprog. Det kan forespørge, transformere, omforme, filtrere og beregne nye værdier. Det er det rigtige værktøj til shell-scripts og engangs-databehandling i en terminal.
- Brug JSONPath, når du skriver applikationskode, der programmatisk skal udtrække felter fra JSON — især når biblioteket skal kunne indlejres og være letvægtigt.
- Brug jq, når du er på en kommandolinje, skriver shell-scripts, debugger API-svar, eller har brug for transformationer (omdøbning af felter, aggregering, opbygning af nye strukturer) der går ud over simpel udtrækning.
- jq-syntaks er mere kraftfuld men også mere kompleks —
jq '.order.lineItems[] | select(.inStock) | .name'vs JSONPath:s$.order.lineItems[?(@.inStock)].name. Til ren udtrækning er JSONPath ofte mere læsbar.
Der er også et mellemterræn: hvis du allerede arbejder i JavaScript og kun har brug for lejlighedsvise
transformationer, dækker noget som Lodash:s _.get()
simpel stihenvisning uden afhængigheder. Men for noget der involverer jokertegn, rekursion
eller filterudtryk er et ordentligt JSONPath-bibliotek det værd.
Opsummering
JSONPath udfylder et reelt hul mellem "gennemgå manuelt dette objekt i en for-løkke" og
"start en jq-proces". Når du er fortrolig med $, ., [*],
.. og [?(filter)], vil du gribe efter det hele tiden —
særligt når du arbejder med store API-nyttelaster, hvor du kun har brug for en håndfuld felter.
RFC 9535-standardiseringen
betyder, at syntaksen nu er stabil, så udtryk du skriver i dag bør fungere konsekvent på tværs af
kompatible biblioteker.
Prøv dine udtryk med JSON Path-værktøjet — indsæt din JSON,
skriv en sti og se resultaterne øjeblikkeligt. Og hvis din JSON skal renses op først,
er JSON Formatter og JSON Validator
også lige ved hånden.