312 lines
8.3 KiB
Markdown
312 lines
8.3 KiB
Markdown
|
# Go JSON Schema Reflection
|
||
|
|
||
|
[![CI](https://github.com/alecthomas/jsonschema/actions/workflows/ci.yml/badge.svg)](https://github.com/alecthomas/jsonschema/actions/workflows/ci.yml)
|
||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/jsonschema)](https://goreportcard.com/report/github.com/alecthomas/jsonschema)
|
||
|
[![GoDoc](https://godoc.org/github.com/alecthomas/jsonschema?status.svg)](https://godoc.org/github.com/alecthomas/jsonschema)
|
||
|
|
||
|
This package can be used to generate [JSON Schemas](http://json-schema.org/latest/json-schema-validation.html) from Go types through reflection.
|
||
|
|
||
|
- Supports arbitrarily complex types, including `interface{}`, maps, slices, etc.
|
||
|
- Supports json-schema features such as minLength, maxLength, pattern, format, etc.
|
||
|
- Supports simple string and numeric enums.
|
||
|
- Supports custom property fields via the `jsonschema_extras` struct tag.
|
||
|
|
||
|
## Example
|
||
|
|
||
|
The following Go type:
|
||
|
|
||
|
```go
|
||
|
type TestUser struct {
|
||
|
ID int `json:"id"`
|
||
|
Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"`
|
||
|
Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"`
|
||
|
Tags map[string]interface{} `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"`
|
||
|
BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"`
|
||
|
YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"`
|
||
|
Metadata interface{} `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"`
|
||
|
FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"`
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Results in following JSON Schema:
|
||
|
|
||
|
```go
|
||
|
jsonschema.Reflect(&TestUser{})
|
||
|
```
|
||
|
|
||
|
```json
|
||
|
{
|
||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||
|
"$ref": "#/definitions/TestUser",
|
||
|
"definitions": {
|
||
|
"TestUser": {
|
||
|
"type": "object",
|
||
|
"properties": {
|
||
|
"metadata": {
|
||
|
"oneOf": [
|
||
|
{
|
||
|
"type": "string"
|
||
|
},
|
||
|
{
|
||
|
"type": "array"
|
||
|
}
|
||
|
]
|
||
|
},
|
||
|
"birth_date": {
|
||
|
"type": "string",
|
||
|
"format": "date-time"
|
||
|
},
|
||
|
"friends": {
|
||
|
"type": "array",
|
||
|
"items": {
|
||
|
"type": "integer"
|
||
|
},
|
||
|
"description": "The list of IDs, omitted when empty"
|
||
|
},
|
||
|
"id": {
|
||
|
"type": "integer"
|
||
|
},
|
||
|
"name": {
|
||
|
"type": "string",
|
||
|
"title": "the name",
|
||
|
"description": "The name of a friend",
|
||
|
"default": "alex",
|
||
|
"examples": [
|
||
|
"joe",
|
||
|
"lucy"
|
||
|
]
|
||
|
},
|
||
|
"tags": {
|
||
|
"type": "object",
|
||
|
"patternProperties": {
|
||
|
".*": {
|
||
|
"additionalProperties": true
|
||
|
}
|
||
|
},
|
||
|
"a": "b",
|
||
|
"foo": [
|
||
|
"bar",
|
||
|
"bar1"
|
||
|
]
|
||
|
},
|
||
|
"fav_color": {
|
||
|
"type": "string",
|
||
|
"enum": [
|
||
|
"red",
|
||
|
"green",
|
||
|
"blue"
|
||
|
]
|
||
|
}
|
||
|
},
|
||
|
"additionalProperties": false,
|
||
|
"required": ["id", "name"],
|
||
|
"oneOf": [
|
||
|
{
|
||
|
"required": [
|
||
|
"birth_date"
|
||
|
],
|
||
|
"title": "date"
|
||
|
},
|
||
|
{
|
||
|
"required": [
|
||
|
"year_of_birth"
|
||
|
],
|
||
|
"title": "year"
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
## Configurable behaviour
|
||
|
|
||
|
The behaviour of the schema generator can be altered with parameters when a `jsonschema.Reflector`
|
||
|
instance is created.
|
||
|
|
||
|
### ExpandedStruct
|
||
|
|
||
|
If set to ```true```, makes the top level struct not to reference itself in the definitions. But type passed should be a struct type.
|
||
|
|
||
|
eg.
|
||
|
|
||
|
```go
|
||
|
type GrandfatherType struct {
|
||
|
FamilyName string `json:"family_name" jsonschema:"required"`
|
||
|
}
|
||
|
|
||
|
type SomeBaseType struct {
|
||
|
SomeBaseProperty int `json:"some_base_property"`
|
||
|
// The jsonschema required tag is nonsensical for private and ignored properties.
|
||
|
// Their presence here tests that the fields *will not* be required in the output
|
||
|
// schema, even if they are tagged required.
|
||
|
somePrivateBaseProperty string `json:"i_am_private" jsonschema:"required"`
|
||
|
SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"`
|
||
|
SomeSchemaIgnoredProperty string `jsonschema:"-,required"`
|
||
|
SomeUntaggedBaseProperty bool `jsonschema:"required"`
|
||
|
someUnexportedUntaggedBaseProperty bool
|
||
|
Grandfather GrandfatherType `json:"grand"`
|
||
|
}
|
||
|
```
|
||
|
|
||
|
will output:
|
||
|
|
||
|
```json
|
||
|
{
|
||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||
|
"required": [
|
||
|
"some_base_property",
|
||
|
"grand",
|
||
|
"SomeUntaggedBaseProperty"
|
||
|
],
|
||
|
"properties": {
|
||
|
"SomeUntaggedBaseProperty": {
|
||
|
"type": "boolean"
|
||
|
},
|
||
|
"grand": {
|
||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||
|
"$ref": "#/definitions/GrandfatherType"
|
||
|
},
|
||
|
"some_base_property": {
|
||
|
"type": "integer"
|
||
|
}
|
||
|
},
|
||
|
"type": "object",
|
||
|
"definitions": {
|
||
|
"GrandfatherType": {
|
||
|
"required": [
|
||
|
"family_name"
|
||
|
],
|
||
|
"properties": {
|
||
|
"family_name": {
|
||
|
"type": "string"
|
||
|
}
|
||
|
},
|
||
|
"additionalProperties": false,
|
||
|
"type": "object"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### PreferYAMLSchema
|
||
|
|
||
|
JSON schemas can also be used to validate YAML, however YAML frequently uses
|
||
|
different identifiers to JSON indicated by the `yaml:` tag. The `Reflector` will
|
||
|
by default prefer `json:` tags over `yaml:` tags (and only use the latter if the
|
||
|
former are not present). This behavior can be changed via the `PreferYAMLSchema`
|
||
|
flag, that will switch this behavior: `yaml:` tags will be preferred over
|
||
|
`json:` tags.
|
||
|
|
||
|
With `PreferYAMLSchema: true`, the following struct:
|
||
|
```go
|
||
|
type Person struct {
|
||
|
FirstName string `json:"FirstName" yaml:"first_name"`
|
||
|
}
|
||
|
```
|
||
|
|
||
|
would result in this schema:
|
||
|
```json
|
||
|
{
|
||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||
|
"$ref": "#/definitions/TestYamlAndJson",
|
||
|
"definitions": {
|
||
|
"Person": {
|
||
|
"required": ["first_name"],
|
||
|
"properties": {
|
||
|
"first_name": {
|
||
|
"type": "string"
|
||
|
}
|
||
|
},
|
||
|
"additionalProperties": false,
|
||
|
"type": "object"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
whereas without the flag one obtains:
|
||
|
```json
|
||
|
{
|
||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||
|
"$ref": "#/definitions/TestYamlAndJson",
|
||
|
"definitions": {
|
||
|
"Person": {
|
||
|
"required": ["FirstName"],
|
||
|
"properties": {
|
||
|
"first_name": {
|
||
|
"type": "string"
|
||
|
}
|
||
|
},
|
||
|
"additionalProperties": false,
|
||
|
"type": "object"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Custom Type Definitions
|
||
|
|
||
|
Sometimes it can be useful to have custom JSON Marshal and Unmarshal methods in your structs that automatically convert for example a string into an object.
|
||
|
|
||
|
To override auto-generating an object type for your struct, implement the `JSONSchemaType() *Type` method and whatever is defined will be provided in the schema definitions.
|
||
|
|
||
|
Take the following simplified example of a `CompactDate` that only includes the Year and Month:
|
||
|
|
||
|
```go
|
||
|
type CompactDate struct {
|
||
|
Year int
|
||
|
Month int
|
||
|
}
|
||
|
|
||
|
func (d *CompactDate) UnmarshalJSON(data []byte) error {
|
||
|
if len(data) != 9 {
|
||
|
return errors.New("invalid compact date length")
|
||
|
}
|
||
|
var err error
|
||
|
d.Year, err = strconv.Atoi(string(data[1:5]))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
d.Month, err = strconv.Atoi(string(data[7:8]))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (d *CompactDate) MarshalJSON() ([]byte, error) {
|
||
|
buf := new(bytes.Buffer)
|
||
|
buf.WriteByte('"')
|
||
|
buf.WriteString(fmt.Sprintf("%d-%02d", d.Year, d.Month))
|
||
|
buf.WriteByte('"')
|
||
|
return buf.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
func (CompactDate) JSONSchemaType() *Type {
|
||
|
return &Type{
|
||
|
Type: "string",
|
||
|
Title: "Compact Date",
|
||
|
Description: "Short date that only includes year and month",
|
||
|
Pattern: "^[0-9]{4}-[0-1][0-9]$",
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The resulting schema generated for this struct would look like:
|
||
|
|
||
|
```json
|
||
|
{
|
||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||
|
"$ref": "#/definitions/CompactDate",
|
||
|
"definitions": {
|
||
|
"CompactDate": {
|
||
|
"pattern": "^[0-9]{4}-[0-1][0-9]$",
|
||
|
"type": "string",
|
||
|
"title": "Compact Date",
|
||
|
"description": "Short date that only includes year and month"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|