// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.// Use of this source code is governed by an MIT-style// license that can be found in the LICENSE file.package jsonschemaimport ()// A Schema is a JSON schema object.// It supports both draft-07 and the 2020-12 draft specifications:// - Draft-07: https://json-schema.org/draft-07/draft-handrews-json-schema-01// and https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01// - Draft 2020-12: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01// and https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01//// A Schema value may have non-zero values for more than one field:// all relevant non-zero fields are used for validation.// There is one exception to provide more Go type-safety: the Type and Types fields// are mutually exclusive.//// Since this struct is a Go representation of a JSON value, it inherits JSON's// distinction between nil and empty. Nil slices and maps are considered absent,// but empty ones are present and affect validation. For example,//// Schema{Enum: nil}//// is equivalent to an empty schema, so it validates every instance. But//// Schema{Enum: []any{}}//// requires equality to some slice element, so it vacuously rejects every instance.typeSchemastruct {// core ID string`json:"$id,omitempty"` Schema string`json:"$schema,omitempty"` Ref string`json:"$ref,omitempty"` Comment string`json:"$comment,omitempty"` Defs map[string]*Schema`json:"$defs,omitempty"` Definitions map[string]*Schema`json:"definitions,omitempty"`// split draft 7 Dependencies into DependencySchemas and DependencyStrings DependencySchemas map[string]*Schema`json:"-"` DependencyStrings map[string][]string`json:"-"` Anchor string`json:"$anchor,omitempty"` DynamicAnchor string`json:"$dynamicAnchor,omitempty"` DynamicRef string`json:"$dynamicRef,omitempty"` Vocabulary map[string]bool`json:"$vocabulary,omitempty"`// metadata Title string`json:"title,omitempty"` Description string`json:"description,omitempty"` Default json.RawMessage`json:"default,omitempty"` Deprecated bool`json:"deprecated,omitempty"` ReadOnly bool`json:"readOnly,omitempty"` WriteOnly bool`json:"writeOnly,omitempty"` Examples []any`json:"examples,omitempty"`// validation // Use Type for a single type, or Types for multiple types; never both. Type string`json:"-"` Types []string`json:"-"` Enum []any`json:"enum,omitempty"`// Const is *any because a JSON null (Go nil) is a valid value. Const *any`json:"const,omitempty"` MultipleOf *float64`json:"multipleOf,omitempty"` Minimum *float64`json:"minimum,omitempty"` Maximum *float64`json:"maximum,omitempty"` ExclusiveMinimum *float64`json:"exclusiveMinimum,omitempty"` ExclusiveMaximum *float64`json:"exclusiveMaximum,omitempty"` MinLength *int`json:"minLength,omitempty"` MaxLength *int`json:"maxLength,omitempty"` Pattern string`json:"pattern,omitempty"`// arrays PrefixItems []*Schema`json:"prefixItems,omitempty"` Items *Schema`json:"-"` ItemsArray []*Schema`json:"-"` MinItems *int`json:"minItems,omitempty"` MaxItems *int`json:"maxItems,omitempty"` AdditionalItems *Schema`json:"additionalItems,omitempty"` UniqueItems bool`json:"uniqueItems,omitempty"` Contains *Schema`json:"contains,omitempty"` MinContains *int`json:"minContains,omitempty"`// *int, not int: default is 1, not 0 MaxContains *int`json:"maxContains,omitempty"` UnevaluatedItems *Schema`json:"unevaluatedItems,omitempty"`// objects MinProperties *int`json:"minProperties,omitempty"` MaxProperties *int`json:"maxProperties,omitempty"` Required []string`json:"required,omitempty"` DependentRequired map[string][]string`json:"dependentRequired,omitempty"` Properties map[string]*Schema`json:"properties,omitempty"` PatternProperties map[string]*Schema`json:"patternProperties,omitempty"` AdditionalProperties *Schema`json:"additionalProperties,omitempty"` PropertyNames *Schema`json:"propertyNames,omitempty"` UnevaluatedProperties *Schema`json:"unevaluatedProperties,omitempty"`// logic AllOf []*Schema`json:"allOf,omitempty"` AnyOf []*Schema`json:"anyOf,omitempty"` OneOf []*Schema`json:"oneOf,omitempty"` Not *Schema`json:"not,omitempty"`// conditional If *Schema`json:"if,omitempty"` Then *Schema`json:"then,omitempty"` Else *Schema`json:"else,omitempty"` DependentSchemas map[string]*Schema`json:"dependentSchemas,omitempty"`// other // https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.8 ContentEncoding string`json:"contentEncoding,omitempty"` ContentMediaType string`json:"contentMediaType,omitempty"` ContentSchema *Schema`json:"contentSchema,omitempty"`// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7 Format string`json:"format,omitempty"`// Extra allows for additional keywords beyond those specified. Extra map[string]any`json:"-"`// PropertyOrder records the ordering of properties for JSON rendering. // // During [For], PropertyOrder is set to the field order, // if the type used for inference is a struct. // // If PropertyOrder is set, it controls the relative ordering of properties in [Schema.MarshalJSON]. // The rendered JSON first lists any properties that appear in the PropertyOrder slice in the order // they appear, followed by all other properties that do not appear in the PropertyOrder slice in an // undefined but deterministic order. PropertyOrder []string`json:"-"`}// falseSchema returns a new Schema tree that fails to validate any value.func falseSchema() *Schema {return &Schema{Not: &Schema{}}}// anchorInfo records the subschema to which an anchor refers, and whether// the anchor keyword is $anchor or $dynamicAnchor.type anchorInfo struct { schema *Schema dynamic bool}// String returns a short description of the schema.func ( *Schema) () string {if .ID != "" {return .ID }if := cmp.Or(.Anchor, .DynamicAnchor); != "" {returnfmt.Sprintf("anchor %s", ) }return"<anonymous schema>"}// CloneSchemas returns a copy of s.// The copy is shallow except for sub-schemas, which are themelves copied with CloneSchemas.// This allows both s and s.CloneSchemas() to appear as sub-schemas of the same parent.func ( *Schema) () *Schema {if == nil {returnnil } := * := reflect.ValueOf(&)for , := rangeschemaFieldInfos { := .Elem().FieldByIndex(.sf.Index)switch .sf.Type {caseschemaType: := .Interface().(*Schema) .Set(reflect.ValueOf(.()))caseschemaSliceType: := .Interface().([]*Schema) = slices.Clone()for , := range { [] = .() } .Set(reflect.ValueOf())caseschemaMapType: := .Interface().(map[string]*Schema) = maps.Clone()for , := range { [] = .() } .Set(reflect.ValueOf()) } }return &}func ( *Schema) () error {if .Type != "" && .Types != nil {returnerrors.New("both Type and Types are set; at most one should be") }if .Defs != nil && .Definitions != nil {returnerrors.New("both Defs and Definitions are set; at most one should be") }if .Items != nil && .ItemsArray != nil {returnerrors.New("both Items and ItemsArray are set; at most one should be") } := make(map[string]bool)for , := range .PropertyOrder {if , := []; {// Duplicate foundreturnfmt.Errorf("property order slice cannot contain duplicate entries, found duplicate %q", ) } [] = true }for := range .DependencySchemas {// Check if the key exists in the dependency strings mapif , := .DependencyStrings[]; {returnfmt.Errorf("dependency key %q cannot be defined as both a schema and a string array", ) } }returnnil}type schemaWithoutMethods Schema// doesn't implement json.{Unm,M}arshalerfunc ( Schema) () ([]byte, error) {// NOTE: Use a value receiver here to avoid the encoding/json bugs // described in golang/go#22967, golang/go#33993, and golang/go#55890. // With a pointer receiver, MarshalJSON is only called for Schema in // some cases (for example when the field value is addressable, or not // stored as a map value), which leads to inconsistent JSON encoding. // A value receiver makes Schema itself implement json.Marshaler and // ensures that encoding/json always calls this method.if := .basicChecks(); != nil {returnnil, }// Marshal either Type or Types as "type".varanyswitch {case .Type != "": = .Typecase .Types != nil: = .Types }varanyswitch {case .Items != nil: = .Itemscase .ItemsArray != nil: = .ItemsArray }varmap[string]any := len(.DependencySchemas) + len(.DependencyStrings)if > 0 { = make(map[string]any, )for , := range .DependencySchemas { [] = }for , := range .DependencyStrings { [] = } } := struct {any`json:"type,omitempty"`json.Marshaler`json:"properties,omitempty"`map[string]any`json:"dependencies,omitempty"`any`json:"items,omitempty"` *schemaWithoutMethods }{ : , : , : , : (*schemaWithoutMethods)(&), }// Marshal properties, even if the empty map (but not nil).if .Properties != nil { . = orderedProperties{props: .Properties,order: .PropertyOrder, } } , := marshalStructWithMap(&, "Extra")if != nil {returnnil, }// Marshal {} as true and {"not": {}} as false. // It is wasteful to do this here instead of earlier, but much easier.switch {casebytes.Equal(, []byte(`{}`)): = []byte("true")casebytes.Equal(, []byte(`{"not":true}`)): = []byte("false") }return , nil}// orderedProperties is a helper to marshal the properties map in a specific order.type orderedProperties struct { props map[string]*Schema order []string}func ( orderedProperties) () ([]byte, error) {varbytes.Buffer .WriteByte('{') := true := make(map[string]bool, len(.props))// Helper closure to write "key": value := func( string, *Schema) error {if ! { .WriteByte(',') } = false// Marshal the Key , := json.Marshal()if != nil {return } .Write() .WriteByte(':')// Marshal the Value , := json.Marshal()if != nil {return } .Write()returnnil }// Write keys explicitly listed in PropertyOrderfor , := range .order {if , := .props[]; {if := (, ); != nil {returnnil, } [] = true } }// Write any remaining keysvar []stringfor := range .props {if ![] { = append(, ) } }// Sort the slice alphabeticallyslices.Sort()for , := range {if := (, .props[]); != nil {returnnil, } } .WriteByte('}')return .Bytes(), nil}func ( *Schema) ( []byte) error {// A JSON boolean is a valid schema.varboolif := json.Unmarshal(, &); == nil {if {// true is the empty schema, which validates everything. * = Schema{} } else {// false is the schema that validates nothing. * = *falseSchema() }returnnil } := struct {json.RawMessage`json:"type,omitempty"`map[string]json.RawMessage`json:"dependencies,omitempty"`json.RawMessage`json:"items,omitempty"`json.RawMessage`json:"const,omitempty"` *integer`json:"minLength,omitempty"` *integer`json:"maxLength,omitempty"` *integer`json:"minItems,omitempty"` *integer`json:"maxItems,omitempty"` *integer`json:"minProperties,omitempty"` *integer`json:"maxProperties,omitempty"` *integer`json:"minContains,omitempty"` *integer`json:"maxContains,omitempty"` *schemaWithoutMethods }{ : (*schemaWithoutMethods)(), }if := unmarshalStructWithMap(, &, "Extra"); != nil {return }// Unmarshal "type" as either Type or Types.varerroriflen(.) > 0 {switch .[0] {case'"': = json.Unmarshal(., &.Type)case'[': = json.Unmarshal(., &.Types)default: = fmt.Errorf(`invalid value for "type": %q`, .) } }if != nil {return }// Unmarshal "items" as either Items or ItemsArray.iflen(.) > 0 {switch .[0] {case'[':var []*Schema = json.Unmarshal(., &) .ItemsArray = default:varSchema = json.Unmarshal(., &) .Items = & } }if != nil {return }// Unmarshal "Dependencies" values as either string arrays or schemas // and assign them to specific map DependencySchemas or DependencyStrings.for , := range . {iflen() > 0 {switch [0] {case'[':var []string = json.Unmarshal(, &)if .DependencyStrings == nil { .DependencyStrings = make(map[string][]string) } .DependencyStrings[] = default:varSchema = json.Unmarshal(, &)if .DependencySchemas == nil { .DependencySchemas = make(map[string]*Schema) } .DependencySchemas[] = & } }if != nil {return } } := func( **any, json.RawMessage) error {iflen() == 0 {returnnil }ifbytes.Equal(, []byte("null")) { * = new(any)returnnil }returnjson.Unmarshal(, ) }// Setting Const to a pointer to null will marshal properly, but won't // unmarshal: the *any is set to nil, not a pointer to nil.if := (&.Const, .); != nil {return } := func( **int, *integer) {if != nil { * = Ptr(int(*)) } } (&.MinLength, .) (&.MaxLength, .) (&.MinItems, .) (&.MaxItems, .) (&.MinProperties, .) (&.MaxProperties, .) (&.MinContains, .) (&.MaxContains, .)returnnil}type integer int32// for the integer-valued fields of Schemafunc ( *integer) ( []byte) error {iflen() == 0 {// nothing to doreturnnil }// If there is a decimal point, src is a floating-point number.varint64ifbytes.ContainsRune(, '.') {varfloat64if := json.Unmarshal(, &); != nil {returnerrors.New("not a number") } = int64()iffloat64() != {returnerrors.New("not an integer value") } } else {if := json.Unmarshal(, &); != nil {returnerrors.New("cannot be unmarshaled into an int") } }// Ensure behavior is the same on both 32-bit and 64-bit systems.if < math.MinInt32 || > math.MaxInt32 {returnerrors.New("integer is out of range") } * = integer()returnnil}// Ptr returns a pointer to a new variable whose value is x.func [ any]( ) * { return & }// every applies f preorder to every schema under s including s.// The second argument to f is the path to the schema appended to the argument path.// It stops when f returns false.func ( *Schema) ( func(*Schema) bool) bool {return () && .everyChild(func( *Schema) bool { return .() })}// everyChild reports whether f is true for every immediate child schema of s.func ( *Schema) ( func(*Schema) bool) bool { := reflect.ValueOf()for , := rangeschemaFieldInfos { := .Elem().FieldByIndex(.sf.Index)switch .sf.Type {caseschemaType:// A field that contains an individual schema. A nil is valid: it just means the field isn't present. := .Interface().(*Schema)if != nil && !() {returnfalse }caseschemaSliceType: := .Interface().([]*Schema)for , := range {if !() {returnfalse } }caseschemaMapType:// Sort keys for determinism. := .Interface().(map[string]*Schema)for , := rangeslices.Sorted(maps.Keys()) {if !([]) {returnfalse } } } }returntrue}// all wraps every in an iterator.func ( *Schema) () iter.Seq[*Schema] {returnfunc( func(*Schema) bool) { .every() }}// children wraps everyChild in an iterator.func ( *Schema) () iter.Seq[*Schema] {returnfunc( func(*Schema) bool) { .everyChild() }}var ( schemaType = reflect.TypeFor[*Schema]() schemaSliceType = reflect.TypeFor[[]*Schema]() schemaMapType = reflect.TypeFor[map[string]*Schema]())type structFieldInfo struct { sf reflect.StructField jsonName string}var (// the visible fields of Schema that have a JSON name, sorted by that name schemaFieldInfos []structFieldInfo// map from JSON name to field schemaFieldMap = map[string]reflect.StructField{})func init() { := reflect.VisibleFields(reflect.TypeFor[Schema]())for , := range { := fieldJSONInfo()if !.omit {schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{, .name}) } else {// jsoninfo.name is used to build the info paths. The items and dependencies are ommited, // since the original fields are separated to handle the union types supported in json and // these fields have custom marshalling and unmarshalling logic. // we still need these fields in schemaFieldInfos for creating schema trees and calculating paths and refs. // so we manually create them and assign the jsonName to the original field json name.switch .Name {case"Items", "ItemsArray":schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{, "items"})case"DependencySchemas", "DependencyStrings":schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{, "dependencies"}) } } }// The value of "dependencies" this sort of schemaFieldInfos. // This sort is unstable and is comparing the json.names of DependencyStrings and DependencySchemas which are both "dependencies". // Since the sort is unstable it cannot be guarantied that "dependencies" has the DependencySchemas value.slices.SortFunc(schemaFieldInfos, func(, structFieldInfo) int {returncmp.Compare(.jsonName, .jsonName) })for , := rangeschemaFieldInfos {schemaFieldMap[.jsonName] = .sf }}
The pages are generated with Goldsv0.8.4. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.