jsonschema

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Apr 15, 2024 License: MIT Imports: 18 Imported by: 0

README

This is a fork of invopop/jsonschema that preserves complete comments for element descriptions.

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

Examples

Constants

This section is empty.

Variables

View Source
var (
	// TrueSchema defines a schema with a true value
	TrueSchema = &Schema{boolean: &[]bool{true}[0]}
	// FalseSchema defines a schema with a false value
	FalseSchema = &Schema{boolean: &[]bool{false}[0]}
)
View Source
var Version = "https://json-schema.org/draft/2020-12/schema"

Version is the JSON Schema version.

Functions

func ExtractGoComments

func ExtractGoComments(base, path string, commentMap map[string]string) error

ExtractGoComments will read all the go files contained in the provided path, including sub-directories, in order to generate a dictionary of comments associated with Types and Fields. The results will be added to the `commentsMap` provided in the parameters and expected to be used for Schema "description" fields.

The `go/parser` library is used to extract all the comments and unfortunately doesn't have a built-in way to determine the fully qualified name of a package. The `base` paremeter, the URL used to import that package, is thus required to be able to match reflected types.

When parsing type comments, we use the `go/doc`'s Synopsis method to extract the first phrase only. Field comments, which tend to be much shorter, will include everything.

func NewProperties

func NewProperties() *orderedmap.OrderedMap[string, *Schema]

NewProperties is a helper method to instantiate a new properties ordered map.

func ToSnakeCase

func ToSnakeCase(str string) string

ToSnakeCase converts the provided string into snake case using dashes. This is useful for Schema IDs and definitions to be coherent with common JSON Schema examples.

Types

type Definitions

type Definitions map[string]*Schema

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 ID

type ID string

ID represents a Schema ID type which should always be a URI. See draft-bhutton-json-schema-00 section 8.2.1

const EmptyID ID = ""

EmptyID is used to explicitly define an ID with no value.

func (ID) Add

func (id ID) Add(path string) ID

Add appends the provided path to the id, and removes any anchor data that might be there.

func (ID) Anchor

func (id ID) Anchor(name string) ID

Anchor sets the anchor part of the schema URI.

func (ID) Base

func (id ID) Base() ID

Base removes any anchor information from the schema

func (ID) Def

func (id ID) Def(name string) ID

Def adds or replaces a definition identifier.

func (ID) String

func (id ID) String() string

String provides string version of ID

func (ID) Validate

func (id ID) Validate() error

Validate is used to check if the ID looks like a proper schema. This is done by parsing the ID as a URL and checking it has all the relevant parts.

type Reflector

type Reflector struct {
	// BaseSchemaID defines the URI that will be used as a base to determine Schema
	// IDs for models. For example, a base Schema ID of `https://invopop.com/schemas`
	// when defined with a struct called `User{}`, will result in a schema with an
	// ID set to `https://invopop.com/schemas/user`.
	//
	// If no `BaseSchemaID` is provided, we'll take the type's complete package path
	// and use that as a base instead. Set `Anonymous` to try if you do not want to
	// include a schema ID.
	BaseSchemaID ID

	// Anonymous when true will hide the auto-generated Schema ID and provide what is
	// known as an "anonymous schema". As a rule, this is not recommended.
	Anonymous bool

	// AssignAnchor when true will use the original struct's name as an anchor inside
	// every definition, including the root schema. These can be useful for having a
	// reference to the original struct's name in CamelCase instead of the snake-case used
	// by default for URI compatibility.
	//
	// Anchors do not appear to be widely used out in the wild, so at this time the
	// anchors themselves will not be used inside generated schema.
	AssignAnchor bool

	// AllowAdditionalProperties will cause the Reflector to generate a schema
	// without additionalProperties set to 'false' 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

	// Do not reference definitions. This will remove the top-level $defs map and
	// instead cause the entire structure of types to be output in one tree. The
	// list of type definitions (`$defs`) will not be included.
	DoNotReference bool

	// ExpandedStruct when true will include the reflected type's definition in the
	// root as opposed to a definition with a reference.
	ExpandedStruct bool

	// FieldNameTag will change the tag used to get field names. json tags are used by default.
	FieldNameTag string

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

	// Lookup allows a function to be defined that will provide a custom mapping of
	// types to Schema IDs. This allows existing schema documents to be referenced
	// by their ID instead of being embedded into the current schema definitions.
	// Reflected types will never be pointers, only underlying elements.
	Lookup func(reflect.Type) ID

	// Mapper is a function that can be used to map custom Go types to jsonschema schemas.
	Mapper func(reflect.Type) *Schema

	// Namer allows customizing of type names. The default is to use the type's name
	// provided by the reflect package.
	Namer func(reflect.Type) string

	// KeyNamer allows customizing of key names.
	// The default is to use the key's name as is, or the json tag if present.
	// If a json tag is present, KeyNamer will receive the tag's name as an argument, not the original key name.
	KeyNamer func(string) string

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

	// CommentMap is a dictionary of fully qualified go types and fields to comment
	// strings that will be used if a description has not already been provided in
	// the tags. Types and fields are added to the package path using "." as a
	// separator.
	//
	// Type descriptions should be defined like:
	//
	//   map[string]string{"github.com/invopop/jsonschema.Reflector": "A Reflector reflects values into a Schema."}
	//
	// And Fields defined as:
	//
	//   map[string]string{"github.com/invopop/jsonschema.Reflector.DoNotReference": "Do not reference definitions."}
	//
	// See also: AddGoComments
	CommentMap map[string]string
}

A Reflector reflects values into a Schema.

func (*Reflector) AddGoComments

func (r *Reflector) AddGoComments(base, path string) error

AddGoComments will update the reflectors comment map with all the comments found in the provided source directories. See the #ExtractGoComments method for more details.

func (*Reflector) Reflect

func (r *Reflector) Reflect(v any) *Schema

Reflect reflects to Schema from a value.

func (*Reflector) ReflectFromType

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

ReflectFromType generates root schema

func (*Reflector) SetBaseSchemaID

func (r *Reflector) SetBaseSchemaID(id string)

SetBaseSchemaID is a helper use to be able to set the reflectors base schema ID from a string as opposed to then ID instance.

type Schema

type Schema struct {
	// RFC draft-bhutton-json-schema-00
	Version     string      `json:"$schema,omitempty"`     // section 8.1.1
	ID          ID          `json:"$id,omitempty"`         // section 8.2.1
	Anchor      string      `json:"$anchor,omitempty"`     // section 8.2.2
	Ref         string      `json:"$ref,omitempty"`        // section 8.2.3.1
	DynamicRef  string      `json:"$dynamicRef,omitempty"` // section 8.2.3.2
	Definitions Definitions `json:"$defs,omitempty"`       // section 8.2.4
	Comments    string      `json:"$comment,omitempty"`    // section 8.3
	// RFC draft-bhutton-json-schema-00 section 10.2.1 (Sub-schemas with logic)
	AllOf []*Schema `json:"allOf,omitempty"` // section 10.2.1.1
	AnyOf []*Schema `json:"anyOf,omitempty"` // section 10.2.1.2
	OneOf []*Schema `json:"oneOf,omitempty"` // section 10.2.1.3
	Not   *Schema   `json:"not,omitempty"`   // section 10.2.1.4
	// RFC draft-bhutton-json-schema-00 section 10.2.2 (Apply sub-schemas conditionally)
	If               *Schema            `json:"if,omitempty"`               // section 10.2.2.1
	Then             *Schema            `json:"then,omitempty"`             // section 10.2.2.2
	Else             *Schema            `json:"else,omitempty"`             // section 10.2.2.3
	DependentSchemas map[string]*Schema `json:"dependentSchemas,omitempty"` // section 10.2.2.4
	// RFC draft-bhutton-json-schema-00 section 10.3.1 (arrays)
	PrefixItems []*Schema `json:"prefixItems,omitempty"` // section 10.3.1.1
	Items       *Schema   `json:"items,omitempty"`       // section 10.3.1.2  (replaces additionalItems)
	Contains    *Schema   `json:"contains,omitempty"`    // section 10.3.1.3
	// RFC draft-bhutton-json-schema-00 section 10.3.2 (sub-schemas)
	Properties           *orderedmap.OrderedMap[string, *Schema] `json:"properties,omitempty"`           // section 10.3.2.1
	PatternProperties    map[string]*Schema                      `json:"patternProperties,omitempty"`    // section 10.3.2.2
	AdditionalProperties *Schema                                 `json:"additionalProperties,omitempty"` // section 10.3.2.3
	PropertyNames        *Schema                                 `json:"propertyNames,omitempty"`        // section 10.3.2.4
	// RFC draft-bhutton-json-schema-validation-00, section 6
	Type              string              `json:"type,omitempty"`              // section 6.1.1
	Enum              []any               `json:"enum,omitempty"`              // section 6.1.2
	Const             any                 `json:"const,omitempty"`             // section 6.1.3
	MultipleOf        json.Number         `json:"multipleOf,omitempty"`        // section 6.2.1
	Maximum           json.Number         `json:"maximum,omitempty"`           // section 6.2.2
	ExclusiveMaximum  json.Number         `json:"exclusiveMaximum,omitempty"`  // section 6.2.3
	Minimum           json.Number         `json:"minimum,omitempty"`           // section 6.2.4
	ExclusiveMinimum  json.Number         `json:"exclusiveMinimum,omitempty"`  // section 6.2.5
	MaxLength         *uint64             `json:"maxLength,omitempty"`         // section 6.3.1
	MinLength         *uint64             `json:"minLength,omitempty"`         // section 6.3.2
	Pattern           string              `json:"pattern,omitempty"`           // section 6.3.3
	MaxItems          *uint64             `json:"maxItems,omitempty"`          // section 6.4.1
	MinItems          *uint64             `json:"minItems,omitempty"`          // section 6.4.2
	UniqueItems       bool                `json:"uniqueItems,omitempty"`       // section 6.4.3
	MaxContains       *uint64             `json:"maxContains,omitempty"`       // section 6.4.4
	MinContains       *uint64             `json:"minContains,omitempty"`       // section 6.4.5
	MaxProperties     *uint64             `json:"maxProperties,omitempty"`     // section 6.5.1
	MinProperties     *uint64             `json:"minProperties,omitempty"`     // section 6.5.2
	Required          []string            `json:"required,omitempty"`          // section 6.5.3
	DependentRequired map[string][]string `json:"dependentRequired,omitempty"` // section 6.5.4
	// RFC draft-bhutton-json-schema-validation-00, section 7
	Format string `json:"format,omitempty"`
	// RFC draft-bhutton-json-schema-validation-00, section 8
	ContentEncoding  string  `json:"contentEncoding,omitempty"`  // section 8.3
	ContentMediaType string  `json:"contentMediaType,omitempty"` // section 8.4
	ContentSchema    *Schema `json:"contentSchema,omitempty"`    // section 8.5
	// RFC draft-bhutton-json-schema-validation-00, section 9
	Title       string `json:"title,omitempty"`       // section 9.1
	Description string `json:"description,omitempty"` // section 9.1
	Default     any    `json:"default,omitempty"`     // section 9.2
	Deprecated  bool   `json:"deprecated,omitempty"`  // section 9.3
	ReadOnly    bool   `json:"readOnly,omitempty"`    // section 9.4
	WriteOnly   bool   `json:"writeOnly,omitempty"`   // section 9.4
	Examples    []any  `json:"examples,omitempty"`    // section 9.5

	Extras map[string]any `json:"-"`
	// contains filtered or unexported fields
}

Schema represents a JSON Schema object type. RFC draft-bhutton-json-schema-00 section 4.3

func Reflect

func Reflect(v any) *Schema

Reflect reflects to Schema from a value using the default Reflector

Example
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/impostorkeanu/jsonschema"
)

type SampleUser 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]any `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    any            `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"`
	FavColor    string         `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"`
}

func main() {
	s := jsonschema.Reflect(&SampleUser{})
	data, err := json.MarshalIndent(s, "", "  ")
	if err != nil {
		panic(err.Error())
	}
	fmt.Println(string(data))
}
Output:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/invopop/jsonschema_test/sample-user",
  "$ref": "#/$defs/SampleUser",
  "$defs": {
    "SampleUser": {
      "oneOf": [
        {
          "required": [
            "birth_date"
          ],
          "title": "date"
        },
        {
          "required": [
            "year_of_birth"
          ],
          "title": "year"
        }
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string",
          "title": "the name",
          "description": "The name of a friend",
          "default": "alex",
          "examples": [
            "joe",
            "lucy"
          ]
        },
        "friends": {
          "items": {
            "type": "integer"
          },
          "type": "array",
          "description": "The list of IDs, omitted when empty"
        },
        "tags": {
          "type": "object",
          "a": "b",
          "foo": [
            "bar",
            "bar1"
          ]
        },
        "birth_date": {
          "type": "string",
          "format": "date-time"
        },
        "year_of_birth": {
          "type": "string"
        },
        "metadata": {
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "array"
            }
          ]
        },
        "fav_color": {
          "type": "string",
          "enum": [
            "red",
            "green",
            "blue"
          ]
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "id",
        "name"
      ]
    }
  }
}

func ReflectFromType

func ReflectFromType(t reflect.Type) *Schema

ReflectFromType generates root schema using the default Reflector

func (*Schema) MarshalJSON

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

MarshalJSON is used to serialize a schema object or boolean.

func (*Schema) UnmarshalJSON

func (t *Schema) UnmarshalJSON(data []byte) error

UnmarshalJSON is used to parse a schema object or boolean.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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