Валидация в Superstruct строится вокруг декларативного описания
структур данных. Помимо примитивов, массивов и объектов, отдельный слой
занимает проверка того, является ли значение экземпляром определённого
конструктора. Для этого используется механизм, основанный на операторе
instanceof, который инкапсулируется в специализированную
структуру.
Такая проверка необходима в случаях, когда данные выходят за рамки JSON-подобных объектов и начинают включать поведение: классы, встроенные типы JavaScript, пользовательские сущности с методами и состоянием.
В Superstruct для проверки экземпляров используется фабрика
instance.
import { instance, assert } from 'superstruct'
class UserService {}
const UserServiceStruct = instance(UserService)
assert(new UserService(), UserServiceStruct) // проходит проверку
assert({}, UserServiceStruct) // ошибка валидации
Внутри реализуется стандартная проверка:
value instanceof Constructor
где Constructor — переданный аргумент.
Ключевая особенность заключается в том, что проверяется именно цепочка прототипов, а не структура объекта.
Использование instance наиболее очевидно в контексте
доменных моделей и сервисов.
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl
}
request() {
return this.baseUrl
}
}
const ApiClientStruct = instance(ApiClient)
const client = new ApiClient('https://api.example.com')
Проверка гарантирует, что значение не просто похоже на объект клиента, а действительно создано через соответствующий конструктор:
assert(client, ApiClientStruct) // корректно
assert({ baseUrl: 'https://api.example.com' }, ApiClientStruct) // ошибка
Такой подход исключает подмену объектов с аналогичной формой, но отсутствующей логикой.
Особое значение имеет взаимодействие с наследованием. Проверка через
instanceof учитывает цепочку прототипов, поэтому экземпляры
дочерних классов проходят проверку базового конструктора.
class Base {}
class Derived extends Base {}
const BaseStruct = instance(Base)
assert(new Derived(), BaseStruct) // проходит проверку
Это поведение следует стандартной модели JavaScript и важно учитывать при проектировании структур: проверка не ограничивает строго конкретный класс, если используется наследование.
Механизм instance применяется не только к
пользовательским классам, но и к встроенным объектам языка.
const DateStruct = instance(Date)
assert(new Date(), DateStruct) // успешно
assert(Date.now(), DateStruct) // ошибка
Значение должно быть именно объектом Date, а не числовым
timestamp.
const ErrorStruct = instance(Error)
assert(new Error('fail'), ErrorStruct) // проходит
Это особенно полезно при обработке результатов операций, где ошибки передаются как объекты, а не строки.
const MapStruct = instance(Map)
const SetStruct = instance(Set)
Такие структуры часто используются в более сложных слоях приложения, где требуется гарантировать сохранение поведения коллекций.
instance редко используется изолированно. Чаще он
становится частью сложных схем, где данные комбинируются с объектными и
примитивными типами.
import { object, string, number, instance } from 'superstruct'
class Logger {
log(message) {
console.log(message)
}
}
const LoggerStruct = instance(Logger)
const ConfigStruct = object({
name: string(),
retries: number(),
logger: LoggerStruct
})
Такая композиция позволяет явно отделить данные от поведенческих компонентов, сохраняя строгую типизацию на уровне выполнения.
Одним из практических сценариев является валидация зависимостей, передаваемых в функции или сервисы.
function createController(logger) {
assert(logger, instance(Logger))
return {
handle() {
logger.log('handled')
}
}
}
В этом случае контракт функции становится явным: переданный объект обязан быть полноценным экземпляром класса, а не имитацией.
Использование instance связано с рядом технических
ограничений Jav * aScript:
Потеря прототипа При сериализации и десериализации (например, через JSON) экземпляры классов превращаются в обычные объекты, и проверка перестаёт проходить.
Множественные контексты выполнения В окружениях
с несколькими realm (iframe, worker) instanceof может
давать неожиданные результаты, так как конструкторы считаются
разными.
Невозможность структурной проверки Проверяется только происхождение объекта, но не его внутреннее состояние.
class A {}
const a = new A()
a.__proto__ = null
// теперь проверка instance не сработает корректно
Механизм проверки экземпляров занимает промежуточное положение между структурной валидацией и проверкой типов на уровне языка. Он используется там, где данные перестают быть просто данными и становятся носителями поведения.
В отличие от объектных схем, где важно соответствие полей, здесь ключевым фактором выступает происхождение объекта, что позволяет строго контролировать границы между слоями системы и предотвращать подмену компонентов, обладающих логикой.