Ключевое слово $defs используется в спецификации JSON
Schema для хранения переиспользуемых схем внутри одного документа. В
библиотеке Ajv $defs позволяет:
$defs пришёл на смену устаревшему
definitions, использовавшемуся в старых версиях JSON
Schema.
$defsПростейшая схема с определением через $defs:
const schema = {
type: "object",
properties: {
user: {
$ref: "#/$defs/user"
}
},
required: ["user"],
$defs: {
user: {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" }
},
required: ["name", "age"],
additionalProperties: false
}
}
}
Проверка:
import Ajv from "ajv"
const ajv = new Ajv()
const validate = ajv.compile(schema)
console.log(
validate({
user: {
name: "Alex",
age: 25
}
})
)
$ref$defs почти всегда используется вместе с
$ref.
Пример ссылки:
{
$ref: "#/$defs/user"
}
Разбор пути:
| Часть | Значение |
|---|---|
# |
текущий документ |
/$defs |
переход к разделу $defs |
/user |
схема user |
Ajv заменяет $ref содержимым соответствующей схемы.
Без $defs:
const schema = {
type: "object",
properties: {
author: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" }
}
},
editor: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" }
}
}
}
}
Проблемы:
С $defs:
const schema = {
type: "object",
properties: {
author: {
$ref: "#/$defs/person"
},
editor: {
$ref: "#/$defs/person"
}
},
$defs: {
person: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" }
},
required: ["name", "email"]
}
}
}
$defs может содержать множество схем.
const schema = {
type: "object",
properties: {
user: {
$ref: "#/$defs/user"
},
product: {
$ref: "#/$defs/product"
}
},
$defs: {
user: {
type: "object",
properties: {
id: { type: "number" },
name: { type: "string" }
}
},
product: {
type: "object",
properties: {
title: { type: "string" },
price: { type: "number" }
}
}
}
}
Схемы в $defs могут ссылаться друг на друга.
const schema = {
$defs: {
address: {
type: "object",
properties: {
city: { type: "string" },
street: { type: "string" }
},
required: ["city", "street"]
},
user: {
type: "object",
properties: {
name: { type: "string" },
address: {
$ref: "#/$defs/address"
}
},
required: ["name", "address"]
}
},
type: "object",
properties: {
user: {
$ref: "#/$defs/user"
}
}
}
$defs
в массивахconst schema = {
type: "array",
items: {
$ref: "#/$defs/product"
},
$defs: {
product: {
type: "object",
properties: {
title: { type: "string" },
price: { type: "number" }
},
required: ["title", "price"]
}
}
}
Проверяемые данные:
[
{
title: "Phone",
price: 500
},
{
title: "Laptop",
price: 1500
}
]
$defs особенно полезен в больших структурах.
const schema = {
type: "object",
properties: {
order: {
$ref: "#/$defs/order"
}
},
$defs: {
user: {
type: "object",
properties: {
id: { type: "integer" },
name: { type: "string" }
},
required: ["id", "name"]
},
product: {
type: "object",
properties: {
sku: { type: "string" },
price: { type: "number" }
},
required: ["sku", "price"]
},
orderItem: {
type: "object",
properties: {
product: {
$ref: "#/$defs/product"
},
quantity: {
type: "integer",
minimum: 1
}
},
required: ["product", "quantity"]
},
order: {
type: "object",
properties: {
customer: {
$ref: "#/$defs/user"
},
items: {
type: "array",
items: {
$ref: "#/$defs/orderItem"
}
}
},
required: ["customer", "items"]
}
}
}
Ajv поддерживает рекурсивные ссылки.
Пример дерева категорий:
const schema = {
$defs: {
category: {
type: "object",
properties: {
name: {
type: "string"
},
children: {
type: "array",
items: {
$ref: "#/$defs/category"
}
}
},
required: ["name"]
}
},
$ref: "#/$defs/category"
}
Пример данных:
{
name: "Electronics",
children: [
{
name: "Phones"
},
{
name: "Laptops",
children: [
{
name: "Gaming"
}
]
}
]
}
allOfconst schema = {
$defs: {
entity: {
type: "object",
properties: {
id: {
type: "integer"
}
},
required: ["id"]
},
userData: {
type: "object",
properties: {
name: {
type: "string"
}
},
required: ["name"]
}
},
allOf: [
{
$ref: "#/$defs/entity"
},
{
$ref: "#/$defs/userData"
}
]
}
Результат:
{
id: 1,
name: "Alex"
}
anyOfconst schema = {
$defs: {
emailContact: {
type: "object",
properties: {
email: {
type: "string",
format: "email"
}
},
required: ["email"]
},
phoneContact: {
type: "object",
properties: {
phone: {
type: "string"
}
},
required: ["phone"]
}
},
anyOf: [
{
$ref: "#/$defs/emailContact"
},
{
$ref: "#/$defs/phoneContact"
}
]
}
oneOfconst schema = {
$defs: {
cardPayment: {
type: "object",
properties: {
cardNumber: {
type: "string"
}
},
required: ["cardNumber"]
},
cashPayment: {
type: "object",
properties: {
cash: {
const: true
}
},
required: ["cash"]
}
},
oneOf: [
{
$ref: "#/$defs/cardPayment"
},
{
$ref: "#/$defs/cashPayment"
}
]
}
if / then / elseconst schema = {
type: "object",
properties: {
type: {
type: "string"
}
},
if: {
properties: {
type: {
const: "admin"
}
}
},
then: {
$ref: "#/$defs/admin"
},
else: {
$ref: "#/$defs/user"
},
$defs: {
admin: {
properties: {
accessLevel: {
type: "number"
}
},
required: ["accessLevel"]
},
user: {
properties: {
nickname: {
type: "string"
}
},
required: ["nickname"]
}
}
}
$defsAjv позволяет подключать внешние схемы.
const userSchema = {
$id: "https://example.com/user.schema.json",
$defs: {
profile: {
type: "object",
properties: {
age: {
type: "number"
}
}
}
}
}
Регистрация:
ajv.addSchema(userSchema)
Использование:
const schema = {
$ref: "https://example.com/user.schema.json#/$defs/profile"
}
$defs от
definitionsСтарый вариант:
definitions: {
user: { ... }
}
Современный вариант:
$defs: {
user: { ... }
}
$;Ajv поддерживает оба варианта, но рекомендуется использовать
$defs.
$refНеверно:
$ref: "#/defs/user"
Верно:
$ref: "#/$defs/user"
$ref: "#/$defs/address"
Но:
$defs: {
user: { ... }
}
Ajv выдаст ошибку компиляции.
Некорректная структура:
a -> b -> a
без правильной организации рекурсивной схемы может привести к проблемам валидации.
Некоторые проекты используют старые версии JSON Schema:
В них чаще встречается definitions.
Для Draft 2019-09 и Draft 2020-12 рекомендуется
$defs.
Хорошая практика:
$defs: {
id: { ... },
user: { ... },
product: { ... },
order: { ... }
}
Плохо:
properties: {
user1: { ... },
user2: { ... },
user3: { ... }
}
Хорошо:
properties: {
user1: { $ref: "#/$defs/user" },
user2: { $ref: "#/$defs/user" },
user3: { $ref: "#/$defs/user" }
}
Крупные вложенные структуры лучше выносить:
$defs: {
paymentInfo: { ... }
}
вместо огромных inline-описаний.
$defsAjv компилирует схемы в JavaScript-функции.
Использование $defs:
Особенно заметна польза в:
const schema = {
type: "object",
properties: {
users: {
type: "array",
items: {
$ref: "#/$defs/user"
}
}
},
$defs: {
address: {
type: "object",
properties: {
city: {
type: "string"
},
zip: {
type: "string"
}
},
required: ["city", "zip"]
},
user: {
type: "object",
properties: {
id: {
type: "integer"
},
name: {
type: "string"
},
address: {
$ref: "#/$defs/address"
}
},
required: [
"id",
"name",
"address"
]
}
}
}
Проверяемые данные:
{
users: [
{
id: 1,
name: "Alex",
address: {
city: "Berlin",
zip: "10001"
}
}
]
}
$defs: {
user,
product,
address,
paymentMethod
}
$defs: {
a1,
x,
data2
}
Один из популярных подходов:
$defs: {
primitives: { ... },
entities: { ... },
api: { ... },
responses: { ... }
}
Либо:
$defs: {
User,
Product,
Order,
Invoice
}
Главное правило — единообразие структуры.
Многие генераторы TypeScript-типов используют $defs.
Например:
$defs: {
user: {
type: "object",
properties: {
id: { type: "number" }
}
}
}
может быть преобразовано в:
type User = {
id: number
}
Это особенно важно при:
$defs и OpenAPIOpenAPI активно использует переиспользуемые схемы.
Аналог:
components:
schemas:
По сути выполняет ту же задачу, что и $defs в JSON
Schema.
Ajv часто применяется вместе с:
$defs$defs особенно полезен, если:
Для маленьких одноразовых схем применение $defs не
всегда оправдано, но в средних и крупных проектах это один из ключевых
механизмов организации JSON Schema.