Clients and tools may need an input description without executing Go rules.
JSONSchema exports the representable constraints of your typed schema.
Infer instead describes a Go output type, while FromJSONSchema constructs
a validator for decoded JSON. These are different tasks, not a lossless
round trip. For exact document bytes without defaulting, use
Encoded JSON.
Examples import arkoren.dev/foundation/validate and use an existing context
named ctx.
Export Rules
Describe a display name alongside its runtime bounds:
name := validate.String(func(text *validate.StringBuilder[string]) {
text.Min(3)
text.Max(80)
text.Describe("Public display name")
})
document, err := name.JSONSchema()
On success, the document describes a string, both length bounds, and the
description. Describe sets metadata, not an error message. Export is
deterministic with sorted object keys and a 2020-12 subset; it does not add
provider-specific strict mode or additionalProperties: false.
Check err before publishing bytes. Export checks executable definitions,
including omitted runtime-only and map-key rules. Invalid definitions return
nil bytes and an error wrapping ErrInvalidSchema; encoding failures preserve
the JSON encoder’s cause. Export never runs custom checks, normalizers,
condition/collection predicates, or declaration callbacks. It does not verify
defaults against their rules or normalize default annotations.
Understand Export Limits
Keep the Go schema authoritative for application validation. Conditions,
cross-field comparisons, custom checks, normalizers, and predicate collection
rules are omitted. Not every time, string, file, or map-key constraint can be
represented. URL does not preserve its allowed scheme list; Unique can
export uniqueItems, but UniqueBy cannot export a Go projection.
Even representable settings need not round-trip. Runtime rules accumulate, while repeated annotations use the last value. Go zeros can be absent where decoded JSON zeros are checked. An exported default may violate emitted constraints and fail import even though export succeeded. A Go regexp is copied verbatim, but RE2-only syntax may differ from another validator’s ECMA-262 interpretation. Check downstream import and validation results.
Describe Output
A response type needs shape metadata without input-presence rules. Use Infer:
type Result struct {
Name string `json:"name"`
Elapsed validate.Duration `json:"elapsed"`
Note string `json:"note,omitempty"`
}
result := validate.Infer[Result]()
document, err := result.JSONSchema()
The document describes Elapsed as a string with format go-duration.
Ordinary fields are listed as required unless omitted by omitempty,
omitzero, or a nil-capable embedded owner. This is descriptive: inferred
Validate adds no value checks, and Parse returns its input unchanged rather
than copying arbitrary objects. Both still respect cancellation.
Inference follows default JSON-v2 type encoding: text-marshaled values are
strings, time.Time is date-time, builtin-byte sequences are strings, and
ordinary structs, arrays, slices, maps, and pointers compose. Defined uint8
elements become array elements rather than builtin bytes. Numeric fields tagged
string become strings; fields with a format option remain {} because
formatter-specific encodings are not modeled. Recursive types stop at a repeated
occurrence with {}, not $ref.
Custom JSON marshalers remain opaque {} unless a named container explicitly
declares its native shape through JSONSchemaType() reflect.Type. Eager
foundation collection.Slice and collection.Map implement that protocol;
lazy collections do not. A provider must deterministically return its identical
unnamed container type independent of receiver contents. Nil, mismatched results,
or provider panics become inference errors.
Raw time.Duration, channels, functions, complex numbers, and unsupported map
keys cannot be inferred. result.Err(), execution, and export return an error
wrapping ErrInvalidSchema for unsupported types. Shape metadata neither proves
that a particular value encodes nor models every encoder option. For runtime
named-container constraints, use Named.
Import a Contract
Use FromJSONSchema when your input is already decoded JSON rather than a typed
Go form. A small contract can bound a list of tags:
tags, err := validate.FromJSONSchema([]byte(`{
"type": "array",
"maxItems": 10,
"items": {"type": "string", "minLength": 1}
}`))
Check construction errors before using the validator. With tags successfully
constructed, validate decoded values directly:
err = tags.Validate(ctx, []any{"go", "queues"})
This passes. Use Parse to retain a copied result with defaults. The input is
never modified. A zero imported validator is not usable.
The importer accepts a deliberate subset: single-string type; properties,
required, schema-valued additionalProperties, and items; enum, const,
default, description, format, and pattern; string-length, numeric,
array-size, and object-size bounds; multipleOf; and uniqueItems. It rejects
unknown keywords, $schema, $ref, compositions such as oneOf, boolean
schemas, type unions, and malformed keyword values with ErrInvalidSchema.
The error identifies the keyword’s JSON Pointer. A pattern must be a string:
empty is valid, null is not. Patterns use Go regular expressions.
Input types are map[string]any, []any, string, float64, bool, and nil.
Go int, int64, and valid numeric encoding/json/jsontext.Value tokens also
count as numbers. This is not reflection-based validation of arbitrary structs.
Integer schemas reject fractions; keywords without type apply only to their
own kinds.
Decoded Presence
Imported application validation deliberately differs from strict JSON Schema
instance checking. required checks whether an object key exists before
defaults: null, empty text, and zero satisfy key existence. Null is otherwise
absent and satisfies every constraint regardless of type; Parse replaces it
with a default when one exists. Missing properties can also receive defaults,
but a missing required key still fails. Zero scalars are supplied, so
minLength: 1 rejects an empty string.
The importer asserts date-time (RFC 3339), email, uri (absolute, with host,
any scheme), uuid, go-duration (time.ParseDuration), and standard duration
(RFC 3339 Appendix A). The duration grammars are not interchangeable. Other
formats annotate only. The separate encoded validator treats all formats as
annotations, so choose the boundary deliberately.
Precision and Uniqueness
Imported numeric literals retain exact decimal values and their original spelling
for comparisons and rerendering. Numeric defaults and diagnostic parameters use
float64 only when its JSON spelling preserves the value; otherwise they use
owned jsontext.Value tokens. Supplied Go numbers retain their concrete types.
Already rounded floating-point input cannot recover lost digits.
uniqueItems uses exact structural keys rather than pairwise comparison.
Object key order is irrelevant, array order matters, and numbers compare by
exact decimal value at every nesting level, including equivalent spellings and
signed zero. Uniqueness checks original items before defaults: parsed defaults
can make previously distinct items equal. Unsupported values or non-finite
numbers inside an item report type at its pointer.
For fixed-size scalar entries, expected uniqueness work/storage is linear; nested objects add encoded-size and key-sorting work. Rerendering sorts object keys while preserving numeric spelling; it is not RFC 8785 canonicalization.
Bound Resource Use
For untrusted contracts or decoded input, independently limit document bytes, numeric digits/exponents, collection sizes, nesting, and concurrent validations. Input must be finite and acyclic. The decoded validator has no implicit global work or memory budget; compilation has no context parameter. Context cancellation alone is not a resource policy.
Use maxItems when the array cardinality is known. After type admission, an
oversized array reports only slice.max for that array and skips uniqueness
and child traversal; sibling values are still checked.
Pass the caller’s context to execution. Traversal, equality, uniqueness, sorting,
and copying observe cancellation, which returns ctx.Err() and discards
accumulated violations; Parse returns nil. Individual scalar operations and
allocations are not preempted. Use Encoded JSON when fixed
byte-validation budgets and exact wire semantics are the required boundary.
For the full export, inference, and import contracts, see the
validate API reference.