Compose.getDefaults Runtime
Validation modes: Validation modes reference
Declaration. Extracts declared default values from a schema's properties, returning a plain object containing only the properties that have a default keyword declared. Properties without defaults are omitted. Nested object properties with their own default-bearing children are recursively traversed.
Use this when you want to pre-populate form fields with schema defaults before the user has provided any input - without running a full coerce that would fail on missing required fields. This is the lightweight alternative to jt.materialize(schema) when you only want explicit defaults and not zero-value synthesis.
Don't use this when you want zero-values for all required fields (use jt.value.create). Don't use it when you want a full materialized instance (use jt.materialize).
Examples
Example 1: Pre-populate a new Book form
BookSchema has currency: 'USD' and inStock: true. isbn, title, authors, and price have no declared defaults - they are omitted from the result.
/**
* Compose.getDefaults — Example 1: Pre-populate form state from canonical schemas
*
* `Compose.getDefaults(schema)` walks the schema's `properties` and
* returns the subset whose `default` keyword is set. Properties
* without an explicit default are absent from the result. Nested
* object properties recurse.
*
* The canonical bookstore puts `default: true` on `Book.inStock` and
* `default: []` on `Customer.addresses` — exercising both the scalar
* and array-default cases.
*/
import { Compose } from '../../../src/index.js';
import {
BookSchema, CustomerSchema
} from '../bookstore/index.js';
const bookDefaults = Compose.getDefaults(BookSchema);
console.assert(
(bookDefaults as { 'inStock'?: boolean }).inStock === true,
'Book.inStock has default true'
);
const customerDefaults = Compose.getDefaults(CustomerSchema);
console.assert(
Array.isArray((customerDefaults as { 'addresses'?: readonly unknown[] }).addresses),
'Customer.addresses has default []'
);
// Form scaffolding: start from declared defaults, leave required fields empty
// so the user fills them in.
const formState = {
...bookDefaults,
'authors': [] as readonly string[],
'inStock': true,
'isbn': '',
'price': {
'amount': 0,
'currency': 'EUR'
},
'printStatus': 'inPrint' as const,
'title': ''
};
console.assert(formState.inStock);
console.assert(formState.title === '');
console.assert(formState.authors.length === 0);
console.assert(formState.isbn === '');
console.assert(formState.price.amount === 0);
console.assert(formState.price.currency === 'EUR');
console.assert(typeof formState.printStatus === 'string');
console.log('Book defaults:', bookDefaults);
console.log('Customer defaults:', customerDefaults);
console.log('Form state seeded from defaults:', formState);
Example 2: Pre-populate an Order form
/**
* Compose.getDefaults — Example 2: Pre-populate an Order form
*
* OrderSchema's top-level properties all $ref into primitives that do
* not declare a default. The extracted defaults object reflects that
* — every required field stays absent for the user to fill in.
*/
import { Compose } from '../../../src/index.js';
import { OrderSchema } from '../bookstore/index.js';
const defaults = Compose.getDefaults(OrderSchema);
console.assert(typeof defaults === 'object');
// orderId, customerId, orderLines, orderTotal, placedAt, shippingAddress have no
// declared default — none appear in the result.
console.assert(!('orderId' in defaults));
console.assert(!('customerId' in defaults));
console.assert(!('orderTotal' in defaults));
console.log('OrderSchema defaults (empty — all fields are user-supplied):', defaults);
Example 3: Nested defaults are traversed
/**
* Compose.getDefaults — Example 3: Nested defaults are traversed
*
* Nested object properties with their own `default`-bearing children
* recurse — each nested level contributes its declared defaults to
* the result. Demonstrates the recursive walk against a settings
* schema that mirrors the canonical bookstore's notification model.
*/
import { Compose } from '../../../src/index.js';
const NotificationSettingsSchema = {
'$id': 'https://bookstore.example/NotificationSettings',
'properties': {
'notifications': {
'properties': {
'email': {
'default': true,
'type': 'boolean'
},
'push': {
'default': false,
'type': 'boolean'
}
},
'type': 'object'
},
'theme': {
'default': 'light',
'type': 'string'
}
},
'type': 'object'
} as const;
const defaults = Compose.getDefaults(NotificationSettingsSchema) as {
'notifications'?: { 'email'?: boolean;
'push'?: boolean };
'theme'?: string;
};
console.assert(defaults.theme === 'light');
console.assert(defaults.notifications?.email === true);
console.assert(defaults.notifications?.push === false);
console.log('NotificationSettings defaults (nested recursion):', defaults);
Bad examples - what NOT to do
Anti-pattern 1: Using getDefaults as a substitute for coerce/materialize
/**
* Compose.getDefaults — Anti-pattern: Treating defaults as a full instance
*
* `getDefaults` returns only declared defaults — never synthesised
* zero-values for required fields. For a blank-but-valid Customer
* scaffold, use `bookstoreEntities.value.create` instead.
*/
import {
bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';
// ✓ Do this — value.create synthesises zero-values for required fields,
// along with declared defaults (here, `addresses: []`).
const blank = bookstoreEntities.value.create(CustomerSchema.$id) as Record<string, unknown>;
console.assert(Array.isArray(blank.addresses));
console.assert(typeof blank.customerId === 'string');
console.assert(typeof blank.email === 'string');
console.assert(typeof blank.name === 'string');
console.log('value.create synthesises all required fields:', {
'addresses': blank.addresses,
'customerId': blank.customerId,
'email': blank.email,
'name': blank.name
});
Comparison
Compose.getDefaults(BookSchema)
// { currency: 'USD', inStock: true }
// Only properties with declared `default` values// Zod has no built-in getDefaults. Closest workaround:
// schema.parse({}) fails on missing required fields.
// Manual extraction hardcodes default values:
const defaults = { currency: 'USD', inStock: true };
// Limitation: hardcoded, not derived from the schema. Must be updated manually
// every time a default changes. No recursive traversal of nested objects.import * as v from 'valibot';
// Limitation: Valibot has no exposed default-extraction helper.
// Defaults attached via v.optional(schema, defaultValue) are applied
// during v.parse but are not enumerable by a public API.
const defaults = { currency: 'USD', inStock: true }; // hardcoded// Limitation: io-ts has no native default-value mechanism. Codecs decode
// inputs as-is; missing properties produce decode errors. Default values
// must be merged in by hand before calling .decode():
const defaults = { currency: 'USD', inStock: true };
const result = BookCodec.decode({ ...defaults, ...input });import { Value } from '@sinclair/typebox/value';
// Value.Default fills ALL fields including zero-values, not just declared defaults:
Value.Default(BookSchema, {})
// → { isbn: undefined, title: undefined, ..., currency: 'USD', inStock: true }
// More fields than getDefaults// AJV has no built-in getDefaults utility.
// Closest workaround: validate an empty object with { useDefaults: true }:
const ajv = new Ajv({ useDefaults: true });
const draft = {};
ajv.validate(bookSchema, draft);
// draft now has defaults injected into it.
// Limitation: modifies the input object in place; requires a full validate call;
// does not return only the declared defaults - fills them into an existing object.# Extract defaults from model fields:
from pydantic._internal._fields import PydanticUndefined
defaults = {
name: field.default
for name, field in Book.model_fields.items()
if field.default is not PydanticUndefined
}
# { 'currency': 'USD', 'in_stock': True }// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.Related
jt.materialize- build a full instance from partial data + defaultsjt.value.create- synthesize zero-values for all required fields + explicit defaults
See also
- Bookstore domain -
BookSchemaandOrderSchemadefaults - Composition index - overview of all composition operations