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:

json
{
  "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:

text
$.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:

text
// 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"
Bemærk om negativt indeksering: $[-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:

text
// 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:

text
// 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:

text
// 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:

text
// 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.

Tjek om et felt eksisterer: For at filtrere elementer, der har et bestemt felt (uanset dets værdi), brug eksistenstjekssyntaksen: [?(@.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:

bash
npm install jsonpath-plus
js
import { 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.88

Bemæ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:

bash
pip install jsonpath-ng
python
from 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 STOCK

Mø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.