This library provides Go structures to marshal/unmarshal and reflect JSON Schema documents.
type MyStruct struct {
Amount float64 `json:"amount" minimum:"10.5" example:"20.6" required:"true"`
Abc string `json:"abc" pattern:"[abc]"`
_ struct{} `additionalProperties:"false"` // Tags of unnamed field are applied to parent schema.
_ struct{} `title:"My Struct" description:"Holds my data."` // Multiple unnamed fields can be used.
}
reflector := jsonschema.Reflector{}
schema, err := reflector.Reflect(MyStruct{})
if err != nil {
log.Fatal(err)
}
j, err := json.MarshalIndent(schema, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(j))
// Output:
// {
// "title": "My Struct",
// "description": "Holds my data.",
// "required": [
// "amount"
// ],
// "additionalProperties": false,
// "properties": {
// "abc": {
// "pattern": "[abc]",
// "type": "string"
// },
// "amount": {
// "examples": [
// 20.6
// ],
// "minimum": 10.5,
// "type": "number"
// }
// },
// "type": "object"
// }By default, JSON Schema is generated from Go struct field types and tags. It works well for the majority of cases, but if it does not there are rich customization options.
type MyObj struct {
BoundedNumber int `query:"boundedNumber" minimum:"-100" maximum:"100"`
SpecialString string `json:"specialString" pattern:"^[a-z]{4}$" minLength:"4" maxLength:"4"`
}Note: field tags are only applied to inline schemas, if you use named type then referenced schema
will be created and tags will be ignored. This happens because referenced schema can be used in
multiple fields with conflicting tags, therefore customization of referenced schema has to done on
the type itself via RawExposer, Exposer or Preparer.
Each tag value has to be put in double quotes ("123").
These tags can be used:
title, stringdescription, stringdefault, can be scalar or JSON valueexample, a scalar value that matches type of parent property, for an array it is applied to itemsexamples, a JSON array valueconst, can be scalar or JSON valuedeprecated, booleanreadOnly, booleanwriteOnly, booleanpattern, stringformat, stringmultipleOf, float > 0maximum, floatminimum, floatmaxLength, integerminLength, integermaxItems, integerminItems, integermaxProperties, integerminProperties, integerexclusiveMaximum, booleanexclusiveMinimum, booleanuniqueItems, booleanenum, tag value must be a JSON or comma-separated list of stringsrequired, boolean, marks property as requirednullable, boolean, overrides nullability of the property
Unnamed fields can be used to configure parent schema:
type MyObj struct {
BoundedNumber int `query:"boundedNumber" minimum:"-100" maximum:"100"`
SpecialString string `json:"specialString" pattern:"^[a-z]{4}$" minLength:"4" maxLength:"4"`
_ struct{} `additionalProperties:"false" description:"MyObj is my object."`
}In case of a structure with multiple name tags, you can enable filtering of unnamed fields with ReflectContext.UnnamedFieldWithTag option and add matching name tags to structure (e.g. query:"_").
type MyObj struct {
BoundedNumber int `query:"boundedNumber" minimum:"-100" maximum:"100"`
SpecialString string `json:"specialString" pattern:"^[a-z]{4}$" minLength:"4" maxLength:"4"`
// These parent schema tags would only be applied to `query` schema reflection (not for `json`).
_ struct{} `query:"_" additionalProperties:"false" description:"MyObj is my object."`
}Complex nested maps and slices/arrays can be configured with path-prefixed field tags.
type MyObj struct {
Ints []int `json:"ints" items.title:"My int"`
ExtraFloats [][]float64 `json:"extra_floats" items.items.title:"My float" items.items.enum:"1.23,4.56"`
MappedStrings map[int]string `json:"mapped_strings" additionalProperties.enum:"abc,def"`
VeryDeep map[int][]string `json:"very_deep" additionalProperties.items.enum:"abc,def"`
}There are a few interfaces that can be implemented on a type to customize JSON Schema generation.
Preparerallows to change generated JSON Schema.Exposeroverrides generated JSON Schema.RawExposeroverrides generated JSON Schema.Describedexposes description.Titledexposes title.Enumexposes enum values.NamedEnumexposes enum values with names.SchemaInlinerinlines schema without creating a definition.IgnoreTypeName, when implemented on a mapped type forces the use of original type for definition name.EmbedReferencer, when implemented on an embedded field type, makes anallOfreference to that type definition.
And a few interfaces to expose subschemas (anyOf, allOf, oneOf, not and if, then, else).
AnyOfExposerexposesanyOfsubschemas.AllOfExposerexposesallOfsubschemas.OneOfExposerexposesoneOfsubschemas.NotExposerexposesnotsubschema.IfExposerexposesifsubschema.ThenExposerexposesthensubschema.ElseExposerexposeselsesubschema.
There are also helper functions
jsonschema.AllOf,
jsonschema.AnyOf,
jsonschema.OneOf
to create exposer instance from multiple values.
Additional centralized configuration is available with
jsonschema.ReflectContext and
Reflect options.
CollectDefinitionsdisables definitions storage in schema and calls user function instead.DefinitionsPrefixsets path prefix for definitions.PropertyNameTagallows using field tags other thanjson.InterceptSchemacalled for every type during schema reflection.InterceptPropcalled for every property during schema reflection.InlineRefstries to inline all references (instead of creating definitions).RootNullableenables nullability of root schema.RootRefconverts root schema to definition reference.StripDefinitionNamePrefixstrips prefix from definition name.PropertyNameMappingexplicit name mapping instead field tags.ProcessWithoutTagsenables processing fields without any tags specified.
Sometimes it is impossible to define a static Go struct, for example when fields are only known at runtime.
Yet, you may need to include such fields in JSON schema reflection pipeline.
For any reflected value, standalone or nested, you can define a virtual structure that would be treated as a native Go struct.
s := jsonschema.Struct{}
s.SetTitle("Test title")
s.SetDescription("Test description")
s.DefName = "TestStruct"
s.Nullable = true
s.Fields = append(s.Fields, jsonschema.Field{
Name: "Foo",
Value: "abc",
Tag: `json:"foo" minLength:"3"`,
})
r := jsonschema.Reflector{}
schema, _ := r.Reflect(s)
j, _ := assertjson.MarshalIndentCompact(schema, "", " ", 80)
fmt.Println("Standalone:", string(j))
type MyStruct struct {
jsonschema.Struct // Can be embedded.
Bar int `json:"bar"`
Nested jsonschema.Struct `json:"nested"` // Can be nested.
}
ms := MyStruct{}
ms.Nested = s
ms.Struct = s
schema, _ = r.Reflect(ms)
j, _ = assertjson.MarshalIndentCompact(schema, "", " ", 80)
fmt.Println("Nested:", string(j))
// Output:
// Standalone: {
// "title":"Test title","description":"Test description",
// "properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
// }
// Nested: {
// "definitions":{
// "TestStruct":{
// "title":"Test title","description":"Test description",
// "properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
// }
// },
// "properties":{
// "bar":{"type":"integer"},"foo":{"minLength":3,"type":"string"},
// "nested":{"$ref":"#/definitions/TestStruct"}
// },
// "type":"object"
// }If you're using additional libraries for validation, like for example
go-playground/validator, you may want to infer validation rules into
documented JSON schema.
type My struct {
Foo *string `json:"foo" validate:"required"`
}Normally, validate:"required" is not recognized, and you'd need to add required:"true" to have the rule exported to
JSON schema.
However, it is possible to extend reflection with custom processing with InterceptProp option.
s, err := r.Reflect(My{}, jsonschema.InterceptProp(func(params jsonschema.InterceptPropParams) error {
if !params.Processed {
return nil
}
if v, ok := params.Field.Tag.Lookup("validate"); ok {
if strings.Contains(v, "required") {
params.ParentSchema.Required = append(params.ParentSchema.Required, params.Name)
}
}
return nil
}))