JSON Toolkit

FAQ dotyczące zapytań i walidacji: JSONPath i Schema

Podsumowanie: Czy zapytania ścieżkowe powinny używać JSONPath czy Pointer, jak wybrać między Patch a Merge Patch, co Schema potrafi zablokować i jak wybrać wersję draftu — wszystko wyjaśnione w jednym przebiegu.

Średniozaawansowany~12 min czytaniaOpublikowano 2026-10-30Zaktualizowano 2026-10-30
Okładka FAQ dotyczącego zapytań i walidacji

Najważniejsze: używaj JSONPath do znajdowania węzłów po warunku, JSON Pointer lub JSON Patch do adresowania i edycji dokładnych lokalizacji, a Merge Patch do scalania konfiguracji; przypinaj nowe Schematy do 2020-12 i włącz allErrors, aby każda porażka wracała w jednym przebiegu.

Odpytywanie to „znajdź to”; walidacja to „potwierdź, że jest poprawne”. Każde ma swój standard, więc ich nie mieszaj.

1. Zapytania ścieżkowe

Jak wybrać między JSONPath a JSON Pointer?

Zależy, czy „znajdujesz po warunku”, czy „pobierasz po pozycji”.

Wymiar JSONPath JSON Pointer
Standard RFC 9535 (2024) RFC 6901 (2013)
Wyrażenie $.users[?(@.age > 18)].id /users/0/id
Symbole wieloznaczne i rekurencja Wspiera *, .. Niewspierane
Filtrowanie warunkowe Wspierane Niewspierane
Wynik Zbiór węzłów (być może kilka) Pojedyncza lokalizacja
Siła wyrazu Silna, ale z ciężkim dziedzictwem różnic implementacji Słaba, ale całkowicie deterministyczna
Typowe użycie Ekstrakcja logów, debugowanie, eksploracja danych JSON Patch, adresowanie konfiguracji, ścieżki błędów

Reguła wyboru jest prosta:

  • Musisz „dostać wszystkie węzły pasujące do warunku” → JSONPath;
  • Musisz „wskazać jedną dokładną lokalizację i dodać, usunąć lub zmodyfikować ją” → JSON Pointer;
  • Używaj ich razem: znajdź węzeł JSONPath i opisz jego położenie Pointerem.

Nie używaj JSONPath do adresowania w kontrakcie API. JSONPath zwraca zbiór, a trzy gałęzie dla wyników pustego, jednoelementowego i wieloelementowego komplikują protokół; Pointer znaczy „albo wskazuje istniejącą lokalizację, albo nie istnieje”, co lepiej pasuje do kontraktu.

Jaka jest różnica między $.. a $.*?

$.. to zejście rekurencyjne i przechodzi każdy poziom; $.* zwraca tylko bezpośrednie dzieci obecnego poziomu.

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* → dwa obiekty {id:1, child:{...}} i {id:3};
  • $..id → trzy wartości: 1, 2, 3;
  • $.*.id → 1 i 3, z pominięciem zagnieżdżonego 2.

Zejście rekurencyjne jest wygodne, ale drogie: $.. przechodzi całe drzewo, co jest O(n) na dużym dokumencie, i łatwo zgarnia głębokie pola, których nie chciałeś. Jeśli wystarczy konkretny poziom, unikaj ...

Czy filtry potrafią porównywać łańcuchy? Czy potrafią dopasowywać regexem?

Porównanie łańcuchów działa; regex zależy od implementacji.

Standardowa składnia (RFC 9535):

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // compare with another node

Porównanie łańcuchów używa podwójnych cudzysłowów (pojedyncze cudzysłowy są też dozwolone w RFC 9535, ale niektóre starsze implementacje akceptują tylko podwójne). Porównanie numeryczne podąża za semantyką numeryczną; mieszanie łańcucha z liczbą, takie jak "10" > 9, jest w standardzie zachowaniem niezdefiniowanym, więc nie polegaj na nim.

Dla regex RFC 9535 definiuje funkcje match() i search(), ale wsparcie dla nich znacznie się różni między implementacjami. Bezpieczne podejście: pozwól JSONPath robić zgrubne filtrowanie, a dopasowanie regex zrób w kodzie po pobraniu wyników.

Czy użyć JSON Patch czy Merge Patch?

Oba opisują „jak coś zmienić”, ale ich siła wyrazu i semantyka są zupełnie różne.

Wymiar JSON Patch (RFC 6902) Merge Patch (RFC 7386)
Forma Tablica operacji [{"op":"add",...}] Obiekt częściowy
Wspierane operacje add / remove / replace / move / copy / test Tylko scalanie (usuwanie przez null)
Adresowanie elementów tablicy Tak, po indeksie Nie, cała tablica jest zastępowana
Atomowość test umożliwia optymistyczne blokowanie Brak
Rozmiar dokumentu Duży, ale precyzyjny Mały, ale ograniczony w sile wyrazu
Dobre do Edycji ziarnistych, częściowych aktualizacji API Scalania konfiguracji, częściowego wysyłania formularza

Kryteria decyzyjne:

  • Zmiana pola trzeciego elementu tablicy → JSON Patch;
  • Scalanie kilku wpisów konfiguracji z istniejącą konfiguracją → Merge Patch;
  • Kontrola współbieżności „zawiedź, jeśli oryginalna wartość nie jest X” → test JSON Patch.

Zauważ pułapkę semantyki null w Merge Patch: null znaczy „usuń ten klucz”, a nie „ustaw wartość na null”. Ustawienie wartości na null jest niemożliwe w Merge Patch.

2. Walidacja schematem

Jaki problem rozwiązuje JSON Schema?

Zamienia „jak dane powinny wyglądać” z ustalenia słownego w kontrakt sprawdzalny maszynowo. Błędy, które naprawdę potrafi zablokować:

  • Brakujące wymagane pola (required);
  • Zły typ, na przykład łańcuch tam, gdzie oczekiwana jest liczba (type);
  • Wartości poza zakresem (minimum / maximum / minLength);
  • Wartości poza dozwolonym zbiorem (enum);
  • Niespójne typy elementów tablicy lub zła długość tablicy (items / minItems);
  • Brakujące pola w zagnieżdżonych strukturach (walidacja rekurencyjna przez properties);
  • Zbędne pola (additionalProperties: false).

Czego nie potrafi rozwiązać: reguł biznesowych („czas końca musi być późniejszy niż czas początku”), warunków między polami (część da się wyrazić przez if/then, ale czytelność cierpi), unikatowości w bazie i sprawdzeń uprawnień.

Więc Schema to odźwierny „kształtu danych”, a nie zamiennik logiki biznesowej.

Czy użyć draft-07 czy 2020-12?

Dla nowych projektów używaj 2020-12; w istniejących systemach sprawdź, co wspiera walidator.

Wersja Status Kluczowe zmiany
draft-04 Przestarzała Wiele starszych bibliotek nadal domyślnie jej używa
draft-07 Standard de facto Najszersze wsparcie, najbogatszy ekosystem
2019-09 Przejściowa Wprowadziła $defs, unevaluatedProperties
2020-12 Obecnie zalecana items rozdzielone na prefixItems + items, wspiera $dynamicRef

Dwie pułapki migracji:

  1. Zmieniła się składnia krotek: w draft-07 items o wartości tablicowej wyrażało krotkę; 2020-12 przeniosło to do prefixItems;
  2. $ref i sąsiednie słowa kluczowe: w draft-07 sąsiednie słowa kluczowe są ignorowane, gdy występuje $ref; 2020-12 pozwala im współistnieć.

Jeśli walidator wspiera tylko draft-07, pisanie składni 2020-12 mimo to jest po cichu ignorowane — a najniebezpieczniejszym przypadkiem jest „walidacja przechodzi”, gdy nic nie było walidowane.

Jak wygenerować typy TypeScript ze Schematu?

Dwie drogi: wyprowadź typy ze Schematu albo wygeneruj Schemat z typów.

Ze Schematu do typów (backend definiuje kontrakt, frontend go konsumuje):

import type { FromSchema } from 'json-schema-to-ts';

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['id'],
  additionalProperties: false,
} as const;

type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }

Klucz to as const: bez niego type: 'string' jest wnioskowane jako string, a nie literał 'string', i wnioskowanie się rozpada.

Z typów do Schematu (frontend ma już typy i potrzebuje walidacji w czasie wykonania):

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);

Zaletą tej drogi jest to, że „jedna definicja daje typy i walidację w czasie wykonania”; kosztem jest wprowadzenie DSL-a, a wygenerowany Schemat może nieść szczegóły implementacyjne (na przykład nieatrakcyjną strukturę $ref).

Jak zlokalizować konkretne pole, gdy walidacja zawodzi?

Spójrz na instancePath (2020-12) lub dataPath (draft-07) zwracane przez walidator; to JSON Pointer do miejsca błędu.

[
  {
    "instancePath": "/items/2/price",
    "schemaPath": "#/properties/items/items/properties/price/type",
    "keyword": "type",
    "message": "must be number"
  }
]

instancePath: "/items/2/price" mówi bezpośrednio: pole price trzeciego elementu tablicy items ma zły typ.

Dwie praktyczne sugestie:

  1. Zwracaj wszystkie błędy naraz, zamiast rzucać przy pierwszym — frontend poprawiający wszystko w jednym przebiegu jest szybszy niż próby tam i z powrotem;
  2. Zamień Pointer na prosty język przed pokazaniem użytkownikom końcowym; /items/2/price jest użyteczne dla deweloperów i bezużyteczne dla użytkowników.

allErrors: true to przełącznik w większości walidatorów (Ajv wymaga jawnego włączenia); domyślność to często tryb fail-fast.

Czy włączać additionalProperties: false?

Włączaj dla kontraktów wewnętrznych; bądź ostrożny z publicznymi API.

Po włączeniu dowolne pole niezadeklarowane w properties zawodzi walidację. Zaletą jest łapanie literówek (userNmae) i nieoczekiwanego przekazywania; wadą jest to, że dodanie pola upstream łamie konsumentów downstream, niszcząc zgodność wsteczną.

Kompromis:

Scenariusz Zalecenie
Między usługami wewnętrznymi Włącz. Zmiany pól przechodzą przez zarządzanie wersjami
Publiczne API zewnętrzne Wyłącz. Pozwól upstream dodawać pola
Konsumpcja danych zewnętrznych Wyłącz. Nie kontrolujesz drugiej strony
Walidacja konfiguracji Włącz. Literówka w konfiguracji powinna zawodzić natychmiast

Jak zwalidować, że elementy tablicy są unikatowe?

Dwie różne semantyki — nie myl ich:

{ "uniqueItems": true }

To deduplikuje cały element, wymagając, by tablica nie zawierała dwóch głęboko równych elementów.

{
  "type": "array",
  "items": { "type": "object", "properties": { "id": { "type": "string" } } },
  "uniqueItems": true
}

Powyższy fragment gwarantuje tylko, że obiekty jako całość się nie powtarzają; {"id":"a","name":"x"} i {"id":"a","name":"y"} nie liczą się jako duplikaty.

Deduplikacji po konkretnym polu Schema nie potrafi wyrazić; musisz walidować w warstwie biznesowej:

const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
  throw new Error('id 重复');
}

Czy format naprawdę waliduje e-maile i daty?

Niekoniecznie. W specyfikacji format domyślnie jest „adnotacją”, a nie „asercją”, więc walidator może je zebrać, nie walidując.

  • Ajv wymaga instalacji i konfiguracji osobnego pakietu ajv-formats, inaczej format: "email" jest faktycznie bezwładne;
  • Niektóre walidatory traktują format jako asercję włączoną domyślnie, więc zachowanie jest niespójne;
  • Nawet gdy włączony, regex email jest bardzo luźny — a@b przechodzi.

Wniosek: nie traktuj format jako silnej walidacji. Rób ścisłą walidację e-maili, URL-i i dat w kodzie biznesowym; trzymanie format w Schemacie jako dokumentacji jest warte zachodu, ale nie oczekuj, że zablokuje brudne dane.

3. Szybka ściągawka

Pytanie Wniosek
Znajdowanie węzłów po warunku JSONPath
Precyzyjne adresowanie, stosowanie patchy JSON Pointer
Zmiana elementów tablicy JSON Patch, Merge Patch tego nie potrafi
Scalanie konfiguracji Merge Patch
Wersja Schema dla nowych projektów 2020-12, pamiętaj o prefixItems
Generowanie typów TS ze Schematu json-schema-to-ts + as const
Lokalizowanie błędów walidacji Czytaj instancePath, włącz allErrors
Deduplikacja po polu Schema nie potrafi; użyj Set w warstwie biznesowej
format: email Większość walidatorów domyślnie tego nie sprawdza, więc nie polegaj