JSON Toolkit

Часті питання про запити й перевірку: JSONPath і Schema

Головне: Чи мають шляхові запити використовувати JSONPath чи Pointer, як обирати між Patch і Merge Patch, що Schema може заблокувати й як обрати версію чернетки — усе в одному проході.

Середній~12 хв читанняОпубліковано 2026-10-30Оновлено 2026-10-30
Обкладинка частих питань про запити й перевірку

Коротко: використовуйте JSONPath, щоб знаходити вузли за умовою, JSON Pointer чи JSON Patch, щоб адресувати й редагувати точні місця, і Merge Patch, щоб зливати конфігурацію; закріплюйте нові Schema на 2020-12 і вмикайте allErrors, щоб кожен збій повертався за один прохід.

Запит — це «знайти», перевірка — це «підтвердити правильність». Кожен має власний стандарт, тож не змішуйте їх.

1. Шляхові запити

Як обирати між JSONPath і JSON Pointer?

Залежить від того, чи ви «знаходите за умовою», чи «дістаєте за позицією».

Вимір JSONPath JSON Pointer
Стандарт RFC 9535 (2024) RFC 6901 (2013)
Вираз $.users[?(@.age > 18)].id /users/0/id
Шаблони й рекурсія Підтримує *, .. Не підтримує
Умовна фільтрація Підтримує Не підтримує
Результат Набір вузлів (можливо, кілька) Одне місце
Виразність Сильна, але з важкою спадщиною відмінностей реалізацій Слабка, але цілком детермінована
Типове використання Вилучення з логів, налагодження, дослідження даних JSON Patch, адресація конфігурації, шляхи помилок

Правило вибору прямолінійне:

  • Треба «дістати всі вузли, що відповідають умові» → JSONPath;
  • Треба «закріпити одне точне місце й додати, видалити чи змінити його» → JSON Pointer;
  • Використовуйте їх разом: знаходьте вузол через JSONPath і описуйте його місце через Pointer.

Не використовуйте JSONPath для адресації в контракті API. JSONPath повертає набір, і три гілки для порожнього, одноелементного й багатоелементного результату ускладнюють протокол; Pointer означає «або вказує на наявне місце, або не існує», що краще підходить для контракту.

Яка різниця між $.. і $.*?

$.. — рекурсивний спуск, що обходить кожен рівень; $.* повертає лише безпосередніх дітей поточного рівня.

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* → два обʼєкти {id:1, child:{...}} і {id:3};
  • $..id → три значення: 1, 2, 3;
  • $.*.id → 1 і 3, за винятком вкладеного 2.

Рекурсивний спуск зручний, але дорогий: $.. обходить усе дерево, що є O(n) на великому документі, і легко вигрібає глибокі поля, яких ви не мали на увазі. Якщо конкретний рівень працює, уникайте ...

Чи можуть фільтри порівнювати рядки? Чи можуть вони зіставляти регулярним виразом?

Порівняння рядків працює; регулярні вирази залежать від реалізації.

Стандартний синтаксис (RFC 9535):

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // порівняння з іншим вузлом

Порівняння рядків використовує подвійні лапки (одинарні лапки теж дозволені в RFC 9535, але деякі старіші реалізації приймають лише подвійні). Числове порівняння слідує числовій семантиці; змішування рядка з числом, як-от "10" > 9, є невизначеною поведінкою в стандарті, тож не покладайтеся на це.

Для регулярних виразів RFC 9535 визначає функції match() і search(), але їхня підтримка вельми різниться між реалізаціями. Безпечний підхід: дозвольте JSONPath робити грубу фільтрацію, а зіставлення регулярним виразом робіть у коді після отримання результатів.

Використовувати JSON Patch чи Merge Patch?

Обидва описують «як щось змінити», але їхня виразність і семантика цілком різні.

Вимір JSON Patch (RFC 6902) Merge Patch (RFC 7386)
Форма Масив операцій [{"op":"add",...}] Частковий обʼєкт
Підтримувані операції add / remove / replace / move / copy / test Лише злиття (видалення через null)
Чи може адресувати елементи масиву Так, за індексом Ні, весь масив замінюється
Атомарність test уможливлює оптимістичне блокування Немає
Розмір документа Великий, але точний Малий, але обмежений у виразності
Добре для Дрібнозернистих редакцій, часткових оновлень API Злиття конфігурації, часткового надсилання форми

Критерії рішення:

  • Змінити поле третього елемента масиву → JSON Patch;
  • Злити кілька записів конфігурації в наявну конфігурацію → Merge Patch;
  • Отримати контроль конкурентності «збій, якщо оригінальне значення не X» → test JSON Patch.

Зауважте пастку в семантиці null Merge Patch: null означає «видалити цей ключ», а не «встановити значення null». Встановити значення null через Merge Patch неможливо.

2. Перевірка Schema

Які проблеми вирішує JSON Schema?

Вона перетворює «як мають виглядати дані» з усної домовленості на машиноперевірюваний контракт. Помилки, які вона справді може заблокувати:

  • Відсутні обовʼязкові поля (required);
  • Неправильний тип, наприклад рядок там, де очікується число (type);
  • Значення поза діапазоном (minimum / maximum / minLength);
  • Значення поза дозволеним набором (enum);
  • Неузгоджені типи елементів масиву чи неправильна довжина масиву (items / minItems);
  • Відсутні поля у вкладених структурах (рекурсивна перевірка через properties);
  • Зайві поля (additionalProperties: false).

Чого вона не може вирішити: бізнес-правила («час завершення має бути пізніше за час початку»), умови між полями (деякі можна виразити через if/then, але читабельність страждає), унікальність у базі даних і перевірки дозволів.

Тож Schema — воротар «форми даних», а не заміна бізнес-логіки.

Використовувати draft-07 чи 2020-12?

Використовуйте 2020-12 для нових проєктів; для наявних систем перевірте, що підтримує валідатор.

Версія Статус Ключові зміни
draft-04 Застаріла Багато старіших бібліотек усе ще типово її використовують
draft-07 Де-факто стандарт Найширша підтримка, найбагатша екосистема
2019-09 Перехідна Ввела $defs, unevaluatedProperties
2020-12 Нині рекомендована items розділено на prefixItems + items, підтримує $dynamicRef

Дві пастки міграції:

  1. Синтаксис кортежів змінився: у draft-07 items зі значенням-масивом виражав кортеж; 2020-12 переніс це до prefixItems;
  2. $ref і сусідні ключові слова: у draft-07 сусідні ключові слова ігноруються, коли присутній $ref; 2020-12 дозволяє їм співіснувати.

Якщо валідатор підтримує лише draft-07, написання синтаксису 2020-12 усе одно мовчки ігнорується — а найнебезпечніший випадок — «перевірка проходить», коли насправді нічого не перевірялося.

Як згенерувати типи TypeScript зі Schema?

Два шляхи: вивести типи зі Schema або згенерувати Schema з типів.

Зі Schema в типи (бекенд визначає контракт, фронтенд його споживає):

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 }

Ключ — as const: без нього type: 'string' виводиться як string, а не літерал 'string', і виведення руйнується.

З типів у Schema (фронтенд уже має типи й потребує перевірку під час виконання):

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);

Перевага цього шляху — «одне визначення дає і типи, і перевірку під час виконання»; ціна — введення DSL, і згенерована Schema може нести деталі реалізації (наприклад, негарну структуру $ref).

Як локалізувати конкретне поле, коли перевірка зазнає збою?

Подивіться на instancePath (2020-12) або dataPath (draft-07), який повертає валідатор; це JSON Pointer до місця помилки.

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

instancePath: "/items/2/price" прямо каже: поле price третього елемента масиву items має неправильний тип.

Дві практичні поради:

  1. Повертайте всі помилки одразу, а не кидайте на першій — фронтенд, що виправляє все за один прохід, швидше за спроби туди-сюди;
  2. Перетворіть Pointer на просту мову, перш ніж показувати кінцевим користувачам; /items/2/price корисний розробникам і некорисний користувачам.

allErrors: true — перемикач у більшості валідаторів (Ajv вимагає ввімкнути його явно); типово часто режим fail-fast.

Чи вмикати additionalProperties: false?

Увімкніть для внутрішніх контрактів; будьте обережні з публічними API.

Щойно ввімкнено, будь-яке поле, не оголошене в properties, провалює перевірку. Виграш — ловить друкарські помилки (userNmae) і неочікуване наскрізне проходження; недолік — додавання поля вище за течією ламає споживачів нижче за течією, руйнуючи зворотну сумісність.

Компроміс:

Сценарій Рекомендація
Між внутрішніми сервісами Увімкніть. Зміни полів проходять через керування версіями
Публічні зовнішні API Вимкніть. Дозвольте вищому за течією додавати поля
Споживання даних третіх сторін Вимкніть. Ви не контролюєте іншу сторону
Перевірка конфігурації Увімкніть. Друкарська помилка в конфігурації має провалюватися негайно

Як перевірити, що елементи масиву унікальні?

Дві різні семантики — не сплутайте:

{ "uniqueItems": true }

Це дедуплікує весь елемент, вимагаючи, щоб масив не містив двох глибоко рівних елементів.

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

Наведений фрагмент лише гарантує, що обʼєкти як ціле не дубльовані; {"id":"a","name":"x"} і {"id":"a","name":"y"} не рахуються дублікатами.

Дедуплікацію за конкретним полем Schema виразити не може; треба перевіряти в бізнес-шарі:

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

Чи format справді перевіряє email і дати?

Не обовʼязково. У специфікації format за замовчуванням є «анотацією», а не «твердженням», тож валідатор може його збирати, не перевіряючи.

  • Ajv вимагає встановлення й налаштування окремого пакета ajv-formats, інакше format: "email" фактично інертний;
  • Деякі валідатори трактують format як твердження, увімкнене за замовчуванням, тож поведінка неузгоджена;
  • Навіть коли ввімкнено, регулярний вираз email вельми вільний — a@b проходить.

Висновок: не трактуйте format як сильну перевірку. Робіть строгу перевірку email, URL і дат у бізнес-коді; тримати format у Schema як документацію варте того, але не очікуйте, що він зупинить брудні дані.

3. Стислий довідник

Питання Висновок
Знайти вузли за умовою JSONPath
Точна адресація, застосування патчів JSON Pointer
Змінити елементи масиву JSON Patch, Merge Patch цього не може
Злити конфігурацію Merge Patch
Версія Schema для нових проєктів 2020-12, памʼятайте prefixItems
Генерувати типи TS зі Schema json-schema-to-ts + as const
Локалізувати помилки перевірки Читайте instancePath, увімкніть allErrors
Дедуплікація за полем Schema не може; використовуйте Set у бізнес-шарі
format: email Більшість валідаторів типово його не перевіряють, тож не покладайтеся