How to validate objects and nesting#
Goal: validate object structure, control whether unknown keys are allowed, and validate deeply nested shapes.
Closed by default#
A declared object rejects unknown keys.
TS
const shape = Shape({ a: 1 })
shape({ a: 2, b: 3 }) // throws: property "b" is not allowed
Allow unknown keys: Open#
TS
const { Open } = require('shape')
const shape = Shape(Open({ a: 1 }))
shape({ a: 2, b: 3 }) // → { a: 2, b: 3 }
Go
shape.MustShape(shape.Open(map[string]any{"a": 1}))
An empty object
{}is treated asOpen—it matches any object.
Constrain unknown values: Child#
Child(shape) says "every extra/undeclared value must match this shape".
TS
const { Child } = require('shape') // Number is the JS global, not an export
const shape = Shape(Child(Number, {})) // any keys, all values must be numbers
shape({ x: 1, y: 2 }) // OK
shape({ x: 'a' }) // throws: not of type number
Go
shape.MustShape(shape.Child(shape.Number, map[string]any{}))
Nesting#
Declare nested objects inline; they validate and default recursively.
Shape({
server: {
port: 8080,
tls: { enabled: false },
},
})
Error paths use dot notation, for example server.tls.enabled.
Force closed#
If a shape became open (through Child or Open) and you want it closed again,
wrap it with Closed.
Reshape a declared object: Pick, Omit, Partial, Extend#
Build one object shape out of another, without repeating it. Each returns a new shape; the source is untouched.
TS
const { Shape, Pick, Omit, Partial, Extend, Email } = require('shape')
const User = { id: Number, name: String, role: 'user' }
Shape(Pick(['id', 'name'], User)) // only id and name
Shape(Omit('id', User)) // everything but id
Shape(Partial(User))({}) // → { id: 0, name: '', role: 'user' }
Shape(Extend({ email: Email }, User)) // User plus a required email
Go
user := map[string]any{"id": shape.Number, "name": shape.String, "role": "user"}
shape.MustShape(shape.Pick([]string{"id", "name"}, user))
shape.MustShape(shape.Extend(map[string]any{"email": shape.Email()}, user))
Partial is shallow: a nested object keeps its own required properties.
Extend keeps the base's openness and checks, and only takes the extension's
properties. The steps chain, each producing a new node:
Closed(User).Pick('id').Extend({ v: 1 }).