Si vous avez déjà fixé une réponse d'API profondément imbriquée en vous disant "il me faut juste ce champ enfoui là-dedans" — JSONPath est l'outil qu'il vous faut. C'est un langage de requête pour JSON, similaire dans l'esprit à ce que XPath fait pour XML. Vous écrivez une expression de chemin, vous obtenez les valeurs correspondantes. Pas de boucles, pas de traversée manuelle. Stefan Goessner l'a introduit en 2007, et après des années d'implémentations légèrement incompatibles, il a été officiellement normalisé en tant que RFC 9535 en 2024. Vous pouvez également explorer les expressions JSONPath de manière interactive avec l'outil JSON Path.

Le jeu de données que nous utiliserons tout au long

Plutôt que des exemples abstraits, travaillons avec quelque chose de réaliste — une réponse de commande e-commerce. C'est le type de JSON que vous obtiendriez depuis une API de gestion des commandes :

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
    }
  }
}

L'opérateur racine $ et la navigation de base

Chaque expression JSONPath commence par $, qui fait référence à la racine du document. À partir de là, vous naviguez avec des points. Vous voulez le statut de la commande ? C'est simple :

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

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

$.order.totals.total
// → 166.88

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

Vous pouvez également utiliser la notation entre crochets, utile quand une clé contient des espaces ou des caractères spéciaux, ou lorsque vous accédez aux éléments d'un tableau par index. Les deux styles sont valides selon les règles de syntaxe de la RFC 9535 :

text
// Notation par points
$.order.customer.email

// Notation par crochets — équivalent
$['order']['customer']['email']

// Index de tableau — premier élément de ligne
$.order.lineItems[0].name
// → "Wireless Headphones"

// Dernier élément de ligne (indexation négative — prise en charge dans RFC 9535)
$.order.lineItems[-1].name
// → "Phone Stand Pro"
Attention à l'indexation négative : $[-1:] (syntaxe de tranche) et $.array[-1] (index négatif direct) sont tous deux valides dans RFC 9535, mais certaines anciennes implémentations ne les prennent pas en charge. Testez avec votre bibliothèque spécifique si vous utilisez des index négatifs.

Caractères génériques — Interroger tous les enfants en même temps

Le caractère générique * correspond à tous les éléments à un niveau donné. Au lieu de demander lineItems[0], lineItems[1], etc., vous pouvez tout récupérer en une fois :

text
// Tous les noms d'articles
$.order.lineItems[*].name
// → ["Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]

// Tous les prix unitaires des articles
$.order.lineItems[*].unitPrice
// → [89.99, 12.49, 34.00]

// Tous les SKU des articles
$.order.lineItems[*].sku
// → ["WH-1042", "CB-USB-C", "SC-PRO-7"]

C'est le schéma que vous utiliserez le plus souvent lorsque vous travaillez avec des tableaux dans les réponses d'API. Au lieu de mapper sur un tableau dans le code de votre application, vous pouvez extraire exactement le champ dont vous avez besoin avant qu'il n'atteigne votre logique métier.

Descente récursive — Trouver des valeurs à n'importe quelle profondeur

L'opérateur .. effectue une recherche récursive dans tout l'arbre du document. C'est comme un parcours en profondeur qui collecte chaque nœud correspondant au nom de la clé, peu importe où il se trouve :

text
// Trouver chaque champ "name" n'importe où dans le document
$..name
// → ["Maria Chen", "Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]

// Trouver chaque champ "city" n'importe où
$..city
// → ["Portland"]

// Trouver chaque champ appelé "id" à n'importe quelle profondeur
$..id
// → ["ORD-9182", "CUST-441"]

Remarquez comment $..name trouve à la fois le nom du client et tous les noms de produits — la profondeur ne l'importe pas. C'est puissant pour l'exploration de schémas : quand on vous remet un blob JSON inconnu et que vous voulez trouver toutes les valeurs pour une clé particulière sans connaître la structure. Si vous souhaitez d'abord vérifier le JSON lui-même, le Validateur JSON et le Formateur JSON sont des points de départ pratiques.

Découpage de tableau

JSONPath emprunte la notation de découpage à la Python pour les tableaux. Le format est [start:end:step], où n'importe quelle partie peut être omise. Ceci est documenté dans RFC 9535 § 2.3.5 :

text
// Les deux premiers articles
$.order.lineItems[0:2]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]

// De l'index 1 jusqu'à la fin
$.order.lineItems[1:]
// → [{ sku: "CB-USB-C", ... }, { sku: "SC-PRO-7", ... }]

// Dernier élément uniquement (syntaxe de tranche — largement prise en charge)
$.order.lineItems[-1:]
// → [{ sku: "SC-PRO-7", ... }]

// Un élément sur deux (pas de 2)
$.order.lineItems[0::2]
// → [{ sku: "WH-1042", ... }, { sku: "SC-PRO-7", ... }]

Expressions de filtre — Interroger par condition

C'est là que JSONPath prend tout son sens. La syntaxe de filtre [?(...)] vous permet d'interroger les éléments de tableau par leurs valeurs de champ. Le symbole @ fait référence à l'élément en cours de test. L' article original de 2007 de Stefan Goessner a introduit cette syntaxe, et la RFC 9535 l'a formalisée :

text
// Articles à moins de 50 $
$.order.lineItems[?(@.unitPrice < 50)]
// → [{ sku: "CB-USB-C", unitPrice: 12.49, ... }, { sku: "SC-PRO-7", unitPrice: 34.00, ... }]

// Uniquement les articles actuellement en stock
$.order.lineItems[?(@.inStock == true)]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]

// Articles où qty est supérieur à 1
$.order.lineItems[?(@.qty > 1)]
// → [{ sku: "CB-USB-C", qty: 2, ... }]

// Articles qui ne sont PAS en stock
$.order.lineItems[?(@.inStock == false)].name
// → ["Phone Stand Pro"]

Vous pouvez aussi combiner des conditions. La plupart des implémentations prennent en charge && et || dans les expressions de filtre, vous permettant d'écrire des choses comme [?(@.inStock == true && @.unitPrice < 30)]. Consultez la documentation de votre bibliothèque — le comportement autour du support des opérateurs était l'une des principales incohérences entre les implémentations pré-RFC que la normalisation RFC 9535 visait à corriger.

Vérifier l'existence d'un champ : Pour filtrer les éléments qui ont un champ particulier (quelle que soit sa valeur), utilisez la syntaxe de vérification d'existence : [?(@.fieldName)]. Par exemple, si certains articles avaient un champ discountCode et d'autres non, $.order.lineItems[?(@.discountCode)] retournerait uniquement les articles remisés.

Utiliser JSONPath en JavaScript

Il n'y a pas encore de support JSONPath natif en JavaScript (contrairement à XPath, qui dispose de document.evaluate() dans le navigateur). Vous aurez besoin d'une bibliothèque. Les deux plus populaires sont jsonpath et jsonpath-plus. Le paquet jsonpath-plus est plus activement maintenu et mieux aligné avec RFC 9535 :

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

Notez que JSONPath() retourne toujours un tableau — même quand vous interrogez une seule valeur. C'est voulu : une expression de chemin est un sélecteur, et un sélecteur peut correspondre à zéro, un ou plusieurs nœuds. Récupérez donc toujours result[0] quand vous savez que vous ciblez une valeur unique.

Utiliser JSONPath en Python

En Python, la bibliothèque jsonpath-ng est l'option la plus complète. Elle prend en charge la spécification de base ainsi que la majorité de la syntaxe d'expression de filtre :

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

Le schéma parse() + find() est la bonne approche pour une utilisation en production — compilez l'expression une fois et exécutez-la sur de nombreux documents, plutôt que de réanalyser la chaîne de chemin à chaque fois. Les docs jsonpath-ng sur GitHub donnent plus de détails sur la syntaxe de filtre étendue et la méthode update() pour modifier les nœuds correspondants.

JSONPath vs jq — Lequel choisir ?

Les gens confondent parfois JSONPath et jq — tous deux "interrogent du JSON", mais ils résolvent des problèmes différents. Voici la décomposition pratique :

  • JSONPath est un langage d'expression de chemin conçu pour être intégré dans des applications. Il interroge et extrait des valeurs. Vous l'utilisez dans du code JavaScript, Python, Java, ou dans des fichiers de configuration comme les sélecteurs Kubernetes.
  • jq est un processeur en ligne de commande complet avec son propre langage de programmation. Il peut interroger, transformer, restructurer, filtrer et calculer de nouvelles valeurs. C'est le bon outil pour les scripts shell et la manipulation ponctuelle de données dans un terminal.
  • Utilisez JSONPath quand vous écrivez du code applicatif qui doit extraire des champs de JSON par programmation — surtout quand la bibliothèque doit être intégrable et légère.
  • Utilisez jq quand vous êtes en ligne de commande, écrivez des scripts shell, déboguez des réponses d'API, ou avez besoin de transformations (renommer des champs, agréger, construire de nouvelles structures) qui vont au-delà de la simple extraction.
  • La syntaxe jq est plus puissante mais aussi plus complexe — jq '.order.lineItems[] | select(.inStock) | .name' vs $.order.lineItems[?(@.inStock)].name en JSONPath. Pour la pure extraction, JSONPath est souvent plus lisible.

Il existe aussi un terrain intermédiaire : si vous travaillez déjà en JavaScript et n'avez besoin que de transformations occasionnelles, quelque chose comme _.get() de Lodash couvre l'accès simple aux chemins sans aucune dépendance. Mais pour tout ce qui implique des caractères génériques, la récursion, ou des expressions de filtre, une bibliothèque JSONPath appropriée vaut la peine.

Pour conclure

JSONPath comble un vrai vide entre "traverser manuellement cet objet dans une boucle for" et "démarrer un processus jq". Une fois à l'aise avec $, ., [*], .., et [?(filtre)], vous y recourrez constamment — surtout quand vous travaillez avec de grandes charges utiles d'API dont vous n'avez besoin que d'une poignée de champs. La normalisation RFC 9535 signifie que la syntaxe est maintenant stable, donc les expressions que vous écrivez aujourd'hui devraient fonctionner de manière cohérente dans les bibliothèques conformes. Testez vos expressions avec l'outil JSON Path — collez votre JSON, écrivez un chemin, et voyez les résultats instantanément. Et si votre JSON a besoin d'être nettoyé d'abord, le Formateur JSON et le Validateur JSON sont aussi là.