jsonschema

package module
v0.0.0-...-b3eba01 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2021 License: MIT Imports: 8 Imported by: 0

README

Go JSON Schema Reflection

This project is a for of Alec Thomas' jsonschema. There have been changes to the way jsonschema's are generated which might brake stuff.

Introduction

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

Documentation

Overview

Package jsonschema uses reflection to generate JSON Schemas from Go types [1].

If json tags are present on struct fields, they will be used to infer property names and if a property is required (omitempty is present).

[1] http://json-schema.org/latest/json-schema-validation.html

Index

Constants

This section is empty.

Variables

View Source
var Version = "http://json-schema.org/draft-04/schema#"

Version is the JSON Schema version. If extending JSON Schema with custom values use a custom URI. RFC draft-wright-json-schema-00, section 6

Functions

This section is empty.

Types

type Definitions

type Definitions map[string]*Type

Definitions hold schema definitions. http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26 RFC draft-wright-json-schema-validation-00, section 5.26

type Reflector

type Reflector struct {
	// AllowAdditionalProperties will cause the Reflector to generate a schema
	// with additionalProperties to 'true' for all struct types. This means
	// the presence of additional keys in JSON objects will not cause validation
	// to fail. Note said additional keys will simply be dropped when the
	// validated JSON is unmarshaled.
	AllowAdditionalProperties bool

	// RequiredFromJSONSchemaTags will cause the Reflector to generate a schema
	// that requires any key tagged with `jsonschema:required`, overriding the
	// default of requiring any key *not* tagged with `json:,omitempty`.
	RequiredFromJSONSchemaTags bool

	// YAMLEmbeddedStructs will cause the Reflector to generate a schema that does
	// not inline embedded structs. This should be enabled if the JSON schemas are
	// used with yaml.Marshal/Unmarshal.
	YAMLEmbeddedStructs bool

	// Prefer yaml: tags over json: tags to generate the schema even if json: tags
	// are present
	PreferYAMLSchema bool

	// ExpandedStruct will cause the toplevel definitions of the schema not
	// be referenced itself to a definition.
	ExpandedStruct bool

	// Do not reference definitions.
	// All types are still registered under the "definitions" top-level object,
	// but instead of $ref fields in containing types, the entire definition
	// of the contained type is inserted.
	// This will cause the entire structure of types to be output in one tree.
	DoNotReference bool

	// Use package paths as well as type names, to avoid conflicts.
	// Without this setting, if two packages contain a type with the same name,
	// and both are present in a schema, they will conflict and overwrite in
	// the definition map and produce bad output.  This is particularly
	// noticeable when using DoNotReference.
	FullyQualifyTypeNames bool

	// IgnoredTypes defines a slice of types that should be ignored in the schema,
	// switching to just allowing additional properties instead.
	IgnoredTypes []interface{}

	// TypeMapper is a function that can be used to map custom Go types to jsconschema types.
	TypeMapper func(reflect.Type) *Type

	// TypeNamer allows customizing of type names
	TypeNamer func(reflect.Type) string

	// AdditionalFields allows adding structfields for a given type
	AdditionalFields func(reflect.Type) []reflect.StructField

	// Extra types allow for non-detectable types (oneOf of structs eg) to be referencable
	ExtraTypes map[string]reflect.Type

	// Skip the addition of the $schema property when create a reference
	SkipSchemaOnRef bool

	// indicate you want to use pointers to indicate if a field is nullable or required
	NullableAndRequiredFromPointers bool
}

A Reflector reflects values into a Schema.

func (*Reflector) Reflect

func (r *Reflector) Reflect(v interface{}) *Schema

Reflect reflects to Schema from a value.

func (*Reflector) ReflectFromType

func (r *Reflector) ReflectFromType(t reflect.Type) *Schema

ReflectFromType generates root schema

type Schema

type Schema struct {
	*Type
	Definitions Definitions
}

Schema is the root schema. RFC draft-wright-json-schema-00, section 4.5

func Reflect

func Reflect(v interface{}) *Schema

Reflect reflects to Schema from a value using the default Reflector

func ReflectFromType

func ReflectFromType(t reflect.Type) *Schema

ReflectFromType generates root schema using the default Reflector

func (*Schema) MarshalJSON

func (s *Schema) MarshalJSON() ([]byte, error)

type Type

type Type struct {
	// RFC draft-wright-json-schema-00
	Version string `json:"$schema,omitempty"` // section 6.1
	Ref     string `json:"$ref,omitempty"`    // section 7
	// RFC draft-wright-json-schema-validation-00, section 5
	MultipleOf           int                    `json:"multipleOf,omitempty"`           // section 5.1
	Maximum              int                    `json:"maximum,omitempty"`              // section 5.2
	ExclusiveMaximum     bool                   `json:"exclusiveMaximum,omitempty"`     // section 5.3
	Minimum              int                    `json:"minimum,omitempty"`              // section 5.4
	ExclusiveMinimum     bool                   `json:"exclusiveMinimum,omitempty"`     // section 5.5
	MaxLength            int                    `json:"maxLength,omitempty"`            // section 5.6
	MinLength            int                    `json:"minLength,omitempty"`            // section 5.7
	Pattern              string                 `json:"pattern,omitempty"`              // section 5.8
	AdditionalItems      *Type                  `json:"additionalItems,omitempty"`      // section 5.9
	Items                *Type                  `json:"items,omitempty"`                // section 5.9
	MaxItems             int                    `json:"maxItems,omitempty"`             // section 5.10
	MinItems             int                    `json:"minItems,omitempty"`             // section 5.11
	UniqueItems          bool                   `json:"uniqueItems,omitempty"`          // section 5.12
	MaxProperties        int                    `json:"maxProperties,omitempty"`        // section 5.13
	MinProperties        int                    `json:"minProperties,omitempty"`        // section 5.14
	Required             []string               `json:"required,omitempty"`             // section 5.15
	Properties           *orderedmap.OrderedMap `json:"properties,omitempty"`           // section 5.16
	PatternProperties    map[string]*Type       `json:"patternProperties,omitempty"`    // section 5.17
	AdditionalProperties json.RawMessage        `json:"additionalProperties,omitempty"` // section 5.18
	Dependencies         map[string]*Type       `json:"dependencies,omitempty"`         // section 5.19
	Enum                 []interface{}          `json:"enum,omitempty"`                 // section 5.20
	Type                 string                 `json:"type,omitempty"`                 // section 5.21
	AllOf                []*Type                `json:"allOf,omitempty"`                // section 5.22
	AnyOf                []*Type                `json:"anyOf,omitempty"`                // section 5.23
	OneOf                []*Type                `json:"oneOf,omitempty"`                // section 5.24
	Not                  *Type                  `json:"not,omitempty"`                  // section 5.25
	Definitions          Definitions            `json:"definitions,omitempty"`          // section 5.26
	// RFC draft-wright-json-schema-validation-00, section 6, 7
	Title       string        `json:"title,omitempty"`       // section 6.1
	Description string        `json:"description,omitempty"` // section 6.1
	Default     interface{}   `json:"default,omitempty"`     // section 6.2
	Format      string        `json:"format,omitempty"`      // section 7
	Examples    []interface{} `json:"examples,omitempty"`    // section 7.4
	// RFC draft-wright-json-schema-hyperschema-00, section 4
	Media          *Type  `json:"media,omitempty"`          // section 4.3
	BinaryEncoding string `json:"binaryEncoding,omitempty"` // section 4.3

	Extras map[string]interface{} `json:"-"`
}

Type represents a JSON Schema object type.

func (*Type) MarshalJSON

func (t *Type) MarshalJSON() ([]byte, error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL