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.

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→1i3, z pominięciem zagnieżdżonego2.
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” →
testJSON 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:
- Zmieniła się składnia krotek: w draft-07
itemso wartości tablicowej wyrażało krotkę; 2020-12 przeniosło to doprefixItems; $refi 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:
- 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;
- Zamień Pointer na prosty język przed pokazaniem użytkownikom końcowym;
/items/2/pricejest 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, inaczejformat: "email"jest faktycznie bezwładne; - Niektóre walidatory traktują
formatjako asercję włączoną domyślnie, więc zachowanie jest niespójne; - Nawet gdy włączony, regex
emailjest bardzo luźny —a@bprzechodzi.
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 |