Hvis du noen gang har stirret på et dypt nestet API-svar og tenkt "jeg trenger bare det ene feltet som er begravd der inne" — er JSONPath verktøyet du leter etter. Det er et spørrespråk for JSON, likt i ånd til hvordan XPath fungerer for XML. Du skriver et stiuttrykk, og du får de matchende verdiene tilbake. Ingen løkker, ingen manuell traversering. Stefan Goessner introduserte det i 2007, og etter år med litt inkompatible implementasjoner som sirkulerte rundt, ble det formelt standardisert som RFC 9535 i 2024. Du kan også utforske JSONPath-uttrykk interaktivt med JSON Path-verktøyet.

Datasettet vi vil bruke gjennom hele artikkelen

Fremfor abstrakte eksempler, la oss jobbe med noe realistisk — et e-handelsordresvar. Dette er den typen JSON du ville fått tilbake fra et ordrehåndterings-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
    }
  }
}

Rotoperatoren $ og grunnleggende navigasjon

Hvert JSONPath-uttrykk starter med $, som refererer til dokumentets rot. Derfra navigerer du med punktum. Vil du ha ordrestatus? Det er enkelt:

text
$.order.status
// → "shipped"

$.order.customer.name
// → "Maria Chen"

$.order.totals.total
// → 166.88

$.order.shippingAddress.city
// → "Portland"

Du kan også bruke klammeparentesnotasjon, som er nyttig når en nøkkel inneholder mellomrom eller spesialtegn, eller når du aksesserer arrayelementer via indeks. Begge stiler er gyldige ifølge RFC 9535s syntaksregler:

text
// Punktnotasjon
$.order.customer.email

// Klammeparentesnotasjon — ekvivalent
$['order']['customer']['email']

// Arrayindeks — første linjepost
$.order.lineItems[0].name
// → "Wireless Headphones"

// Siste linjepost (negativ indeksering — støttet i RFC 9535)
$.order.lineItems[-1].name
// → "Phone Stand Pro"
Merk om negativ indeksering: $[-1:] (slice-syntaks) og $.array[-1] (direkte negativt indeks) er begge gyldige i RFC 9535, men noen eldre implementasjoner støtter dem ikke. Test mot ditt spesifikke bibliotek hvis du bruker negative indekser.

Jokertegn — spørr alle barn på én gang

Jokertegnet * matcher alle elementer på et gitt nivå. I stedet for å spørre om lineItems[0], lineItems[1] osv., kan du hente alt på én gang:

text
// Alle linjepostnavn
$.order.lineItems[*].name
// → ["Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]

// Alle linjeposters enhetspriser
$.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 mønsteret du vil bruke oftest når du jobber med arrays i API-svar. I stedet for å mappe over en array i applikasjonskoden din, kan du trekke ut nøyaktig det feltet du trenger før det når forretningslogikken din.

Rekursivt søk — finn verdier på alle dybder

Operatoren .. gjør et rekursivt søk gjennom hele dokumenttreet. Det er som en dybde-først-gjennomgang som samler alle noder som matcher nøkkelnavnet, uavhengig av hvor det befinner seg:

text
// Finn hvert "name"-felt et vilkårlig sted i dokumentet
$..name
// → ["Maria Chen", "Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]

// Finn hvert "city"-felt et vilkårlig sted
$..city
// → ["Portland"]

// Finn hvert felt kalt "id" på alle dybder
$..id
// → ["ORD-9182", "CUST-441"]

Legg merke til hvordan $..name finner både kundenavnet og alle produktnavnene — det bryr seg ikke om dybden. Dette er kraftfullt for skjemautforskning: når du får et ukjent JSON-klump og ønsker å finne alle verdier for en bestemt nøkkel uten å kjenne strukturen. Hvis du vil sjekke selve JSON-filen først, er JSON Validator og JSON Formatter praktiske startpunkter.

Array-slicing

JSONPath låner Python-stilens slice-notasjon for arrays. Formatet er [start:end:step], der enhver del kan utelates. Dette er dokumentert i RFC 9535 § 2.3.5:

text
// Første to linjepostene
$.order.lineItems[0:2]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]

// Fra indeks 1 til slutten
$.order.lineItems[1:]
// → [{ sku: "CB-USB-C", ... }, { sku: "SC-PRO-7", ... }]

// Kun siste element (slice-syntaks — bredt støttet)
$.order.lineItems[-1:]
// → [{ sku: "SC-PRO-7", ... }]

// Hvert annet element (steg på 2)
$.order.lineItems[0::2]
// → [{ sku: "WH-1042", ... }, { sku: "SC-PRO-7", ... }]

Filteruttrykk — spørring etter betingelse

Det er her JSONPath virkelig viser sin styrke. Filtersyntaksen [?(...)] lar deg spørre array-elementer etter feltverdienes verdier. Symbolet @ refererer til det gjeldende elementet som testes. Stefan Goessners originale artikkel fra 2007 introduserte denne syntaksen, og RFC 9535 formaliserte 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 på lager
$.order.lineItems[?(@.inStock == true)]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]

// Varer der qty er større enn 1
$.order.lineItems[?(@.qty > 1)]
// → [{ sku: "CB-USB-C", qty: 2, ... }]

// Varer som IKKE er på lager
$.order.lineItems[?(@.inStock == false)].name
// → ["Phone Stand Pro"]

Du kan også kombinere betingelser. De fleste implementasjoner støtter && og || inne i filteruttrykk, noe som lar deg skrive ting som [?(@.inStock == true && @.unitPrice < 30)]. Sjekk bibliotekets dokumentasjon — atferd rundt operatørstøtte var en av de viktigste inkonsekvensene mellom pre-RFC-implementasjoner som RFC 9535-standardiseringen hadde som mål å fikse.

Sjekke felteksistens: For å filtrere elementer som har et bestemt felt (uavhengig av verdien), bruk eksistenssjekksyntaksen: [?(@.fieldName)]. Hvis for eksempel noen linjepostre hadde et discountCode-felt og andre ikke, ville $.order.lineItems[?(@.discountCode)] returnere bare de rabatterte.

Bruke JSONPath i JavaScript

Det er ingen innebygd JSONPath-støtte i JavaScript ennå (i motsetning til XPath, som har document.evaluate() i nettleseren). Du trenger et bibliotek. De to mest populære er jsonpath og jsonpath-plus. Pakken jsonpath-plus vedlikeholdes mer 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

Merk at JSONPath() alltid returnerer en array — selv når du spør etter én enkelt verdi. Det er med vilje: et stiuttrykk er en selektor, og en selektor kan matche null, én eller mange noder. Så ta alltid result[0] når du vet at du sikter på en unik verdi.

Bruke JSONPath i Python

I Python er biblioteket jsonpath-ng det mest komplette alternativet. Det støtter kjernespesifikasjonen pluss det meste av filteruttrykkssyntaksen:

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ønsteret parse() + find() er riktig tilnærming for produksjonsbruk — kompiler uttrykket én gang og kjør det mot mange dokumenter, fremfor å re-parse stistrengen hver gang. jsonpath-ng-dokumentasjonen på GitHub har mer detalj om utvidet filtersyntaks og metoden update() for å endre matchede noder.

JSONPath vs jq — hvilken bør du bruke?

Folk forveksler noen ganger JSONPath og jq — begge "spør JSON", men de løser forskjellige problemer. Her er den praktiske fordelingen:

  • JSONPath er et stiuttrykksspråk designet for å bygges inn i applikasjoner. Det spør og trekker ut verdier. Du bruker det inne i JavaScript-, Python- eller Java-kode, eller konfigurasjonsfiler som Kubernetes-selektorer.
  • jq er en fullstendig kommandolinjeprosessor med sitt eget programmeringsspråk. Det kan spørre, transformere, omforme, filtrere og beregne nye verdier. Det er det riktige verktøyet for shell-skript og engangs-databehandling i en terminal.
  • Bruk JSONPath når du skriver applikasjonskode som programmatisk trenger å trekke ut felt fra JSON — spesielt når biblioteket trenger å være innleggbart og lettvektig.
  • Bruk jq når du er på kommandolinjen, skriver shell-skript, debugger API-svar, eller trenger å gjøre transformasjoner (omdøping av felt, aggregering, bygging av nye strukturer) som går utover enkel uttrekking.
  • jq-syntaks er mer kraftfull men også mer kompleks — jq '.order.lineItems[] | select(.inStock) | .name' vs JSONPath:s $.order.lineItems[?(@.inStock)].name. For ren uttrekking er JSONPath ofte mer lesbar.

Det er også et midtterreng: hvis du allerede jobber i JavaScript og bare trenger av og til transformasjoner, dekker noe som Lodash:s _.get() enkel stiaksess uten noen avhengigheter. Men for alt som involverer jokertegn, rekursjon eller filteruttrykk er et skikkelig JSONPath-bibliotek verdt det.

Oppsummering

JSONPath fyller et reelt gap mellom "traverser manuelt dette objektet i en for-løkke" og "start en jq-prosess". Når du er komfortabel med $, ., [*], .. og [?(filter)], vil du gripe etter det konstant — spesielt når du jobber med store API-nyttelaster der du bare trenger en håndfull felt. RFC 9535-standardiseringen betyr at syntaksen nå er stabil, så uttrykk du skriver i dag bør fungere konsekvent på tvers av kompatible biblioteker. Prøv uttrykkene dine med JSON Path-verktøyet — lim inn JSON-en din, skriv en sti og se resultatene øyeblikkelig. Og hvis JSON-en din trenger å ryddes opp først, er JSON Formatter og JSON Validator rett der også.