Compose.pick and Compose.omit
pick and omit are inverse operations for creating schema projections. Both return new schema objects - input schemas are never mutated.
Compose.pick
Declaration. Creates a new schema containing only the specified property keys. The required array is filtered to include only keys that were in the original required and appear in the keys argument. Non-picked required fields are dropped. TypeScript infers a type with only the picked properties.
Use this when you need a schema that exposes only a subset of fields - for API projections, list-view summaries, or public interfaces that should not expose all internal fields. This is the runtime equivalent of TypeScript's Pick<T, K>.
Don't use this when you need to remove specific fields while keeping the rest (use omit). Don't use it when you want to add fields (use extend).
Examples
Example 1: Book catalog summary - only display fields
import { Compose, JsonTology } from 'json-tology';
import type { InferType } from 'json-tology/types';
import { BookSchema } from './bookstore/index.js';
const BookSummarySchema = Compose.pick(
BookSchema,
['isbn', 'title', 'price', 'inStock'] as const,
'https://bookstore.example/BookSummary',
);
type BookSummary = InferType<typeof BookSummarySchema>;
// { readonly isbn?: string; readonly title?: string; readonly price?: number; readonly inStock?: boolean }
const jt = JsonTology.create({
baseIRI: 'https://bookstore.example',
schemas: [BookSummarySchema] as const,
});
const summary = jt.instantiate(BookSummarySchema.$id, {
isbn: '9780140449136',
title: 'Crime and Punishment',
price: 14.99,
inStock: true,
authors: ['Dostoevsky'], // not picked - stripped during coerce
});
// { isbn: '...', title: '...', price: 14.99, inStock: true }
// authors is goneExample 2: Customer card for embedding in order responses
import { Compose } from 'json-tology';
import { CustomerSchema } from './bookstore/index.js';
const CustomerCardSchema = Compose.pick(
CustomerSchema,
['id', 'name', 'email'] as const,
'https://bookstore.example/CustomerCard',
);
// Useful for embedding customer info in Order responses without Address dataExample 3: Build sub-schema for partial validation (builds on subschemaAt)
import { Compose, JsonTology } from 'json-tology';
import { ReviewSchema } from './bookstore/index.js';
const ReviewRatingSchema = Compose.pick(
ReviewSchema,
['rating'] as const,
'https://bookstore.example/ReviewRating',
);
const jt2 = JsonTology.create({
baseIRI: 'https://bookstore.example',
schemas: [ReviewRatingSchema] as const,
});
// Validate just the rating field on blur
const errors = jt2.validate(ReviewRatingSchema.$id, { rating: 6 });
// ['/rating: must be <= 5']Bad examples - what NOT to do
Anti-pattern 1: Forgetting as const on the keys array
// ⊥ Don't do this - without as const, keys array widens to string[] and TypeScript loses the literal types
const schema = Compose.pick(BookSchema, ['isbn', 'title'], '...');
// ✓ Do this
const schema2 = Compose.pick(BookSchema, ['isbn', 'title'] as const, '...');Comparison
Compose.pick(BookSchema, ['isbn', 'title', 'price'] as const, 'https://bookstore.example/BookSummary')BookSchema.pick({ isbn: true, title: true, price: true })import * as v from 'valibot';
v.pick(BookSchema, ['isbn', 'title', 'price'])import { Type } from '@sinclair/typebox';
// TypeBox has Type.Pick:
Type.Pick(BookSchema, ['isbn', 'title', 'price'])// Manual construction:
const BookSummary = {
$id: 'BookSummary',
type: 'object',
properties: { isbn: BookSchema.properties.isbn, title: BookSchema.properties.title, price: BookSchema.properties.price },
required: BookSchema.required?.filter(k => ['isbn', 'title', 'price'].includes(k)),
};book.model_dump(include={'isbn', 'title', 'price'})
# Or define a separate model class:
class BookSummary(BaseModel):
isbn: str
title: str
price: floatRelated
omit- inverse: keep everything except specified keysextend- add new propertiespartial- make all properties optional
Compose.omit
Declaration. Creates a new schema with the specified property keys removed from properties. Removed keys are also dropped from required. TypeScript infers a type without the omitted properties.
Use this when you need to remove specific fields while keeping the rest - for example, stripping currency from Book for a region-normalized API, or removing addresses from Customer for a public profile endpoint. This is the runtime equivalent of TypeScript's Omit<T, K>.
Don't use this when you need to keep only specific fields (use pick). Don't use it when you want to add fields (use extend).
Examples
Example 1: Public book without internal currency field
import { Compose } from 'json-tology';
import { BookSchema } from './bookstore/index.js';
const PublicBookSchema = Compose.omit(
BookSchema,
['currency'] as const,
'https://bookstore.example/PublicBook',
);
// All Book properties except currency
// currency was optional (has default) so required array unchangedExample 2: Order summary without line items
import { Compose } from 'json-tology';
import { OrderSchema } from './bookstore/index.js';
const OrderSummarySchema = Compose.omit(
OrderSchema,
['items'] as const,
'https://bookstore.example/OrderSummary',
);
type OrderSummary = InferType<typeof OrderSummarySchema>;
// { id, customerId, total, currency?, placedAt } - no items arrayExample 3: Build a derived schema from a retrieved schema (builds on get)
import { Compose } from 'json-tology';
import { bookstoreEntities as entities, BookSchema } from './bookstore/index.js';
// Retrieve and narrow dynamically
const raw = jt.get(BookSchema.$id);
if (raw) {
const BookWithoutStock = Compose.omit(
raw as typeof BookSchema,
['inStock'] as const,
'https://bookstore.example/BookWithoutStock',
);
jt.register(BookWithoutStock);
}Comparison
Compose.omit(CustomerSchema, ['addresses'] as const, 'https://bookstore.example/CustomerPublic')CustomerSchema.omit({ addresses: true })import * as v from 'valibot';
v.omit(CustomerSchema, ['addresses'])import { Type } from '@sinclair/typebox';
Type.Omit(CustomerSchema, ['addresses'])// Manual - copy schema, delete key from properties and required:
const { addresses: _, ...props } = CustomerSchema.properties;
const req = CustomerSchema.required?.filter(k => k !== 'addresses') ?? [];
const CustomerPublic = { ...CustomerSchema, properties: props, required: req };customer.model_dump(exclude={'addresses'})
# Or define a derived model:
class CustomerPublic(BaseModel):
id: str
email: str
name: strRelated
pick- keep only specified fieldspartial- make remaining fields optional after omitextend- add new properties
See also
- Bookstore domain - where
BookSchema,CustomerSchema,OrderSchemaare defined - Composition index - overview of all composition operations