minus7 / jsonschema

Generate JSON Schemas from Go types

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Go JSON Schema Reflection

CI Go Report Card GoDoc

This package can be used to generate JSON Schemas 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:

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:

jsonschema.Reflect(&TestUser{})
{
  "$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.

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:

{
  "$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:

type Person struct {
	FirstName string `json:"FirstName" yaml:"first_name"`
}

would result in this schema:

{
  "$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:

{
  "$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:

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:

{
  "$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"
    }
  }
}

About

Generate JSON Schemas from Go types

License:MIT License


Languages

Language:Go 96.1%Language:Shell 3.9%