본문으로 건너뛰기

Core Concepts

JSON Schema as the Single Source of Truth​

폼 구조, 필드 타입, 검증 규칙, 조건부 로직은 모두 단일 JSON Schema 객체에 인코딩됩니다. 라이브러리는 마운트 시 이 스키마를 파싱하여 내부 노드 트리를 구성합니다 — 스키마 필드당 하나의 노드. 컴포넌트 코드에서 폼 필드나 검증 규칙을 수동으로 정의할 필요가 없습니다.

const schema = {
type: 'object',
properties: {
title: { type: 'string', minLength: 1 },
price: { type: 'number', minimum: 0 },
inStock: { type: 'boolean' },
tags: { type: 'array', items: { type: 'string' } },
},
required: ['title', 'price'],
};
// → ObjectNode
// ├── StringNode (title)
// ├── NumberNode (price)
// ├── BooleanNode (inStock)
// └── ArrayNode (tags)
// └── StringNode (items)

Node Tree​

각 노드는 자체 값, 검증 오류, 상태 플래그(dirty, touched, validated)를 소유하는 반응형 객체입니다. 변경 사항은 위로 전파됩니다: 리프 노드의 값이 변경되면 모든 상위 노드가 UpdateValue 이벤트를 받고 집계된 값을 업데이트합니다.

Node State Flags​

PropertyTypeDescription
valueanyCurrent value
defaultValueanyValue at initialization
errorsJSONSchemaError[]Validation errors for this node
dirtybooleanValue changed since initialization
touchedbooleanUser has interacted with this field
validatedbooleanValidation has been run
visiblebooleanComputed — controls visibility
activebooleanComputed — controls active state
readOnlybooleanComputed or inherited
disabledbooleanComputed or inherited
initializedbooleanNode has completed initialization
requiredbooleanField is required in parent schema
nullablebooleanSchema type includes "null"

Node Navigation​

// Absolute path from root (RFC 6901 JSONPointer)
const node = rootNode.find('/address/street');

// Relative path from current node
const sibling = node.find('./city');

// Find all matching nodes
const allItems = rootNode.findAll('/items/*');

Plugin Architecture​

라이브러리는 두 개의 독립적인 플러그인 레이어로 분리되어 있습니다. 어떤 validator와 UI 라이브러리도 조합할 수 있습니다.

┌─────────────────────────────────────────────────────┐
│ SchemaFormPlugin │
│ │
│ FormGroup FormLabel FormInput FormError │ ← UI layer
│ formTypeInputDefinitions │
├─────────────────────────────────────────────────────┤
│ ValidatorPlugin │
│ │
│ bind(instance) │ ← Validation layer
│ compile(jsonSchema) → ValidateFunction │
└─────────────────────────────────────────────────────┘

Plugin Merge Behavior​

When multiple plugins are registered, the merge rules are:

PropertyMerge strategy
FormGroup, FormLabel, FormInput, FormErrorLast wins
formTypeInputDefinitionsPrepended (first match wins)
validatorLast wins
formatErrorLast wins

FormType System​

FormTypeInputDefinition은 테스트 조건과 React 컴포넌트를 쌍으로 연결합니다. 라이브러리는 등록된 모든 정의를 우선순위 순서로 평가하고, 첫 번째 매칭 컴포넌트를 사용하여 필드를 렌더링합니다.

// Object test — declarative
const definition: FormTypeInputDefinition = {
test: { type: 'string', format: 'date' }, // matches date fields
Component: DatePickerInput,
};

// Function test — imperative
const definition: FormTypeInputDefinition = {
test: (hint) => hint.jsonSchema.widget === 'richtext',
Component: RichTextEditor,
};

// Hint object available in test function:
type Hint = {
type: JSONSchemaType; // 'string' | 'number' | 'boolean' | ...
path: string; // JSONPointer path
required: boolean;
nullable: boolean;
jsonSchema: JSONSchema;
format?: string;
formType?: string; // custom jsonSchema.formType field
};

FormTypeInputMap (path-based override)​

// Map specific paths to specific components
const formTypeInputMap: FormTypeInputMap = {
'/user/avatar': AvatarUploader,
'/items/*/price': CurrencyInput, // * matches any segment
};

<Form jsonSchema={schema} formTypeInputMap={formTypeInputMap} />

Computed Properties​

필드는 computed (또는 &computed) 키의 JSONPointer 표현식을 사용하여 형제 또는 부모 값에 반응할 수 있습니다. 의존성은 표현식에서 자동으로 추적되거나, watch를 통해 명시적으로 선언됩니다.

{
type: 'object',
properties: {
isPremium: { type: 'boolean' },
discount: {
type: 'number',
computed: {
visible: '../isPremium === true', // show only for premium
readOnly: '../isPremium === false', // lock if not premium
watch: ['../isPremium'], // explicit dep (optional)
},
},
},
}

Supported computed keys: visible, active, readOnly, disabled, watch.

JSONPointer Extensions (computed scope only)​

SyntaxMeaning
../fieldParent node's field
./fieldCurrent node's field (relative)
..Navigate to parent
*Wildcard — any segment (FormTypeInputMap only)

Schema Composition​

라이브러리는 모든 표준 JSON Schema 구성 키워드를 지원합니다. oneOf와 anyOf 브랜치는 커스텀 &if 표현식을 사용하여 활성화를 필드 값에 바인딩할 수 있습니다.

// Standard if/then/else
{
if: { properties: { type: { enum: ['movie'] } } },
then: { properties: { releaseDate: { type: 'string', format: 'date' } } },
else: { properties: { version: { type: 'string' } } },
}

// oneOf with &if expression (exclusive branches)
{
oneOf: [
{ '&if': "./type === 'movie'", properties: { director: { type: 'string' } } },
{ '&if': "./type === 'game'", properties: { platform: { type: 'string' } } },
],
}

// anyOf with &if (non-exclusive, multiple can be active)
{
anyOf: [
{ '&if': './flag1 === true', properties: { field1: { type: 'string' } } },
{ '&if': './flag2 === true', properties: { field2: { type: 'string' } } },
],
}

// allOf (always merged)
{
allOf: [
{ properties: { firstName: { type: 'string' } } },
{ properties: { lastName: { type: 'string' } } },
{ required: ['firstName', 'lastName'] },
],
}

Conditional setValue Filtering​

부모 노드가 setValue()를 호출하면, 활성 oneOf/anyOf 브랜치와 일치하지 않는 필드는 자동으로 값에서 제거됩니다. 자식 노드가 setValue()를 호출하면 이 필터링이 우회됩니다 — 현재 브랜치 조건과 관계없이 값이 전파됩니다.

Array Output Filters​

배열 스키마는 options 아래에 출력 필터를 지정할 수 있습니다. 이 필터는 배열이 내보내는 값 — 부모 전파, 루트 validation, 외부 표면(onChange, getValue(), submit) — 만 정제하며, 자식 노드와 raw node.value는 모든 항목을 유지하므로 미리 노출한 빈 input이 그대로 렌더링됩니다.

const jsonSchema = {
type: 'array',
minItems: 3, // 빈 input 3개가 먼저 렌더링됨
items: { type: 'string' },
options: { omitTrailing: true },
};
// 사용자가 첫 번째 input만 채우면:
// getValue() / onChange / validation 값은 ['first']
  • omitTrailing (opt-in)은 후행 연속 undefined 항목만 제거합니다. 선행·중간 undefined는 보존되어 error dataPath와 validation index 정합이 유지됩니다.
  • omitEmpty (기본 활성)는 빈 배열을 부모 전파 경로에서 undefined로 변환합니다. 필터 순서는 omitTrailing → omitEmpty입니다.
  • 정제된 출력은 node.normalizedValue로 노출되고 node.value는 raw를 유지합니다. 커스텀 배열 FormTypeInput에서는 행 수를 value.length가 아니라 node.children 기준으로 세야 합니다.

Validation System​

ValidationMode​

enum ValidationMode {
None = 0, // No automatic validation
OnChange = 1, // Validate after every setValue()
OnRequest = 2, // Validate only when validate() is called
}

// Default: OnChange | OnRequest (bitwise OR)
<Form validationMode={ValidationMode.OnChange | ValidationMode.OnRequest} />

ShowError​

enum ShowError {
Dirty = 1, // show when node.dirty === true
Touched = 2, // show when node.touched === true
DirtyTouched = 3, // show when both dirty AND touched (default)
}

<Form showError={ShowError.DirtyTouched} /> // default
<Form showError={true} /> // always show
<Form showError={false} /> // never show

Error Structure​

interface JSONSchemaError {
dataPath: string; // JSONPointer to the failing field
keyword: string; // JSON Schema keyword (e.g., 'minLength', 'required')
message: string;
details: any; // keyword-specific details
source: any; // raw error from the validator library
}

Event System​

노드는 배치 이벤트 시스템을 통해 통신합니다. 동일한 동기 호출 스택에서 예약된 이벤트는 단일 배치로 병합되어 다음 마이크로태스크에서 디스패치됩니다. UpdateValue는 예외로, 초기화 완료 후 동기적으로 발생합니다(즉시 모드).

// Key event types (NodeEventType enum)
UpdateValue; // value changed — synchronous after init
UpdateState; // dirty/touched/validated changed
UpdateError; // validation errors changed
UpdateComputedProperties; // computed visible/readOnly/disabled changed
UpdateChildren; // array/object children added or removed
RequestRefresh; // internal: sync uncontrolled component
RequestRemount; // external: force full component remount
AI Agent Reference

AI Concept Glossary​

TermDefinition
SchemaNodeBase interface for all nodes — the public API surface
StringNode / NumberNode / BooleanNode / NullNodeTerminal nodes — no children
ObjectNode / ArrayNodeBranch nodes — have children
VirtualNodeNon-schema node created for conditional branches (oneOf/anyOf)
BranchStrategyStrategy for complex nested structures with child nodes
TerminalStrategyStrategy for simple non-nested structures
FormTypeInputDefinition{ test, Component } pair — maps a schema pattern to a React component
FormTypeInputMap{ [jsonPointerPath]: Component } — path-based component override
FormTypeInputPropsProps interface all custom input components must satisfy
FormTypeRendererPropsProps for FormGroup/FormLabel/FormInput/FormError renderer components
SchemaFormPluginPlugin object with optional renderer components + formTypeInputDefinitions + validator + formatError
ValidatorPlugin{ bind(instance), compile(jsonSchema) → ValidateFunction }
ValidateFunction(value) => JSONSchemaError[] | null | Promise<...>
computed / &computedJSON Schema extension for reactive field properties
&ifJSON Schema extension — expression to activate a oneOf/anyOf branch
enhancedValueValue including virtual/conditional field values — used for validation
ShowErrorEnum controlling when validation errors are displayed
ValidationModeEnum controlling when validation runs
NodeEventTypeEnum of all events nodes can emit
SetValueOptionBitwise options for setValue() — Overwrite, Merge, Isolate
FormHandleRef interface for imperative form control
FormProviderContext provider for sharing plugin config across a subtree
JSONPointerRFC 6901 path string (e.g., /address/street) — extended with .., ., *