jsonschema

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

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

Go to latest
Published: Nov 24, 2023 License: MIT Imports: 21 Imported by: 0

README

Go Reference

This repository the same as flowstack/go-jsonschema, but with a bit better error messages and a few bugs fixed.

go-jsonschema

Go JSON Schema parser and validator

Although go-jsonschema hasn't reached version 1.0, it already passes all mandatory tests and most optional tests in the test suites for Draft 4, Draft 6 and Draft 7.
A lot of work has already been done to parse a lot of draft2019-09 and draft2020-12 tests.

Errors are not very informative.
E.g. line number, keys, values, etc. aren't reported back.
The main focus has been on speed and correctness of validation, but error reporting should get better over time.

Until the release of version 1.0, breaking changes can happen, but will be avoided if possible.

Usage

It is now possible to marshal and unmarshal schemas directly with encoding/json.

Validate JSON Schema
import "github.com/flowstack/go-jsonschema"

func main() {
    schema := `{"properties": {"id": {"type": "string"}}}`

    // Validate a JSON Schema
    _, err := jsonschema.Validate(schema)
    if err != nil {
        log.Fatal(err)
    }
}
Validate JSON against a JSON Schema
import "github.com/flowstack/go-jsonschema"

func main() {
    schema := `{"properties": {"id": {"type": "string"}}}`

    // Create a validator
    validator, err := jsonschema.NewFromString(schema)
    // Alternative: validator, err := jsonschema.New([]byte(schema))
    if err != nil {
        log.Fatal(err)
    }

    // Validate a JSON document against the schema
    json := `{"id": "123abc"}`
    _, err = validator.Validate([]byte(json))
    if err != nil {
        log.Fatal(err)
    }
}
Marshal / Unmarshal
import "github.com/flowstack/go-jsonschema"

func main() {
    schemaStr := `{"properties": {"id": {"type": "string"}}}`

    // Unmarshal from string to Schema struct
	schema := &jsonschema.Schema{}
	err := json.Unmarshal([]byte(schemaStr), schema)
	if err != nil {
		t.Fatal(err)
	}

    // Marshal from Schema struct to JSON
	schemaBytes, err := json.Marshal(schema)
	if err != nil {
		t.Fatal(err)
	}
}

Contributions

Contributions are very welcome! This project is young and could use more eyes and brains to make everything better.
So please fork, code and make pull requests.
At least the existing tests should pass, but you're welcome to change those too, as long as the JSON Schema test suite is run and passes.

Currently most test for Draft 2019-09 and Draft 2020-12 passes, but there is more code to be done, before those 2 will be fully functional.

The JSON Schema parser is fairly slow and could probably be made faster, relatively easy.

Motivation for creating yet another JSON Schema parser / validator

The very nice gojsonschema was missing some features and we needed some internal functionality, that was hard to build on top of gojsonschema.

Furthermore gojsonschema uses Go's JSON parser, which makes it relatively slow, when parsing JSON documents for validation.
This module uses the excellent jsonparser, which is waaaay faster than Go's builtin parser.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewStringPtr

func NewStringPtr(b []byte) *string

func Validate

func Validate(jsonDoc []byte) (bool, error)

Validate will get $schema if it exists or fall back to latest supported Draft

Types

type Dependencies

type Dependencies map[string]*Dependency

func NewDependencies

func NewDependencies(jsonVal []byte, vt jsonparser.ValueType, parentSchema *Schema) (*Dependencies, error)

func (Dependencies) MarshalJSON

func (d Dependencies) MarshalJSON() ([]byte, error)

type Dependency

type Dependency struct {
	Schema  *Schema
	Strings *Strings
}

func NewDependency

func NewDependency(jsonVal []byte, vt jsonparser.ValueType, parentSchema *Schema) (*Dependency, error)

func (Dependency) MarshalJSON

func (i Dependency) MarshalJSON() ([]byte, error)

type Enum

type Enum []*Value

func NewEnum

func NewEnum(jsonVal []byte, vt jsonparser.ValueType) (*Enum, error)

func (Enum) MarshalJSON

func (e Enum) MarshalJSON() ([]byte, error)

type Items

type Items struct {
	Schema  *Schema
	Schemas *Schemas
	Boolean *bool
}

func NewItems

func NewItems(jsonVal []byte, vt jsonparser.ValueType, parentSchema *Schema) (*Items, error)

func (Items) MarshalJSON

func (i Items) MarshalJSON() ([]byte, error)

type NamedProperty

type NamedProperty struct {
	Name     string
	Property *Schema
}

Properties are build like this, instead of map[string]*Schema, to make it possible to Marshal with original sort order of fields

type NamedValue

type NamedValue struct {
	Name string
	*Value
}

type Properties

type Properties []*NamedProperty

func NewProperties

func NewProperties(jsonVal []byte, vt jsonparser.ValueType, parentSchema *Schema) (*Properties, error)

func (Properties) GetProperty

func (p Properties) GetProperty(name string) (*NamedProperty, bool)

func (Properties) MarshalJSON

func (p Properties) MarshalJSON() ([]byte, error)

type Ref

type Ref struct {
	String *string
	Schema *Schema `json:"-"`
	// contains filtered or unexported fields
}

func NewRef

func NewRef(jsonVal []byte, vt jsonparser.ValueType, parentSchema *Schema) (*Ref, error)

func (Ref) MarshalJSON

func (r Ref) MarshalJSON() ([]byte, error)

type Schema

type Schema struct {
	Schema    *string `json:"$schema,omitempty"`
	ID        *string `json:"$id,omitempty"` // NOTE: draft-04 has id instead if $id
	IDDraft04 *string `json:"id,omitempty"`  // NOTE: draft-04 has id instead if $id
	Ref       *Ref    `json:"$ref,omitempty"`
	Comment   *string `json:"$comment,omitempty"`

	Title       *string `json:"title,omitempty"`
	Description *string `json:"description,omitempty"`

	Type *Type `json:"type,omitempty"`

	// Must have at least 1 value
	Enum     *Enum   `json:"enum,omitempty"`
	Default  *Value  `json:"default,omitempty"`
	Examples *Values `json:"examples,omitempty"`
	// Draft 6
	// Only allow 1 value
	Const *Value `json:"const,omitempty"`
	// Draft 7
	ReadOnly    *bool       `json:"readOnly,omitempty"`
	WriteOnly   *bool       `json:"writeOnly,omitempty"`
	Definitions *Properties `json:"definitions,omitempty"`
	// If schemas should look something like (const being the important part):
	//  { "if": { "properties": { "propertyX": { "const": "ValueX" } }, "required": ["propertyX"] } }
	If *Schema `json:"if,omitempty"`
	// One (or both?) of these can be omitted.
	// Both then and else will be ignore, if If is not defined.
	// If any of them are omitted, the value true is used in their place.
	// NOTE: It's not entirely obvious in the documentation, if both can be omitted:
	// https://json-schema.org/understanding-json-schema/reference/conditionals.html#if-then-else
	Then *Schema `json:"then,omitempty"`
	Else *Schema `json:"else,omitempty"`

	AllOf *Schemas `json:"allOf,omitempty"`
	AnyOf *Schemas `json:"anyOf,omitempty"`
	OneOf *Schemas `json:"oneOf,omitempty"`
	Not   *Schema  `json:"not,omitempty"`

	ContentEncoding  *string `json:"contentEncoding,omitempty"`  // e.g. base64
	ContentMediaType *string `json:"contentMediaType,omitempty"` // e.g. image/png

	Properties *Properties `json:"properties,omitempty"`
	// Draft 4 requires at least 1 string
	Required      *Strings `json:"required,omitempty"`
	MaxProperties *int64   `json:"maxProperties,omitempty"`
	MinProperties *int64   `json:"minProperties,omitempty"`
	// Dependencies is either:
	//  - if propertyX is set, then propertyY and propertyZ is required
	//     e.g.: { "propertyX": ["propertyY", "propertyZ"] }
	//  - if propertyX is set, then schemaX is also required to match
	//     e.g.: { "propertyX": { "properties": { "propertyY": { "type": "string" } }, "required": ["propertyY"] } }
	Dependencies *Dependencies `json:"dependencies,omitempty"`
	// patternProperties is used to match property names against a regex and for each a schema.
	// It's basically a map of schemas, but with regex instead of property names.
	PatternProperties *Properties `json:"patternProperties,omitempty"`

	// additionalProperties is a schema that will be used to validate any properties
	//  in the instance that are not matched by properties or patternProperties.
	// Setting it to false means no additional properties will be allowed.
	AdditionalProperties *Schema `json:"additionalProperties,omitempty"`
	//
	// Draft 6
	// Useful for enforcing a certain property name format
	// Property names implies { "type": "string" }
	// "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"}
	PropertyNames *Schema `json:"propertyNames,omitempty"`

	// When items is an array of multiples Schemas, each refers to their own index.
	Items       *Items `json:"items,omitempty"` // TODO: Can actually also be boolean
	MaxItems    *int64 `json:"maxItems,omitempty"`
	MinItems    *int64 `json:"minItems,omitempty"`
	UniqueItems *bool  `json:"uniqueItems,omitempty"`
	// Should only be evaluated when items is multiple schemas.
	// Any values that does not have an explicit schmea (multi schema),
	//  will validate according to this schema.
	// Setting it to false, means that no other values are allowed.
	AdditionalItems *Schema `json:"additionalItems,omitempty"`
	// contains only need to match 1 item in the documents array
	Contains *Schema `json:"contains,omitempty"`

	MaxLength *int64  `json:"maxLength,omitempty"`
	MinLength *int64  `json:"minLength,omitempty"`
	Format    *string `json:"format,omitempty"`
	Pattern   *string `json:"pattern,omitempty"`

	// The type (int/float) should of course match the type of the property
	MultipleOf *json.Number `json:"multipleOf,omitempty"`
	// Draft 4: x ≥ minimum unless exclusiveMinimum == true, x ≤ maximum unless exclusiveMaximum == true
	// Draft 6: x ≥ minimum, x > exclusiveMinimum, x ≤ maximum, x < exclusiveMaximum
	Maximum          *Value `json:"maximum,omitempty"`
	ExclusiveMaximum *Value `json:"exclusiveMaximum,omitempty"` // bool in draft 4
	Minimum          *Value `json:"minimum,omitempty"`
	ExclusiveMinimum *Value `json:"exclusiveMinimum,omitempty"` // bool in draft 4
	// contains filtered or unexported fields
}
var (
	Draft04Schema *Schema
	Draft06Schema *Schema
	Draft07Schema *Schema
)

func New

func New(schema []byte) (*Schema, error)

func NewFromString

func NewFromString(schema string) (*Schema, error)

func (*Schema) AddSchema

func (s *Schema) AddSchema(schema *Schema) error

func (*Schema) AddSchemaString

func (s *Schema) AddSchemaString(schemaString string) error

func (*Schema) DeRef

func (s *Schema) DeRef() error

func (*Schema) ExpandURI

func (s *Schema) ExpandURI(uri string) (*url.URL, error)

ExpandURI attempts to resolve a uri against the current Base URI

func (Schema) GetID

func (s Schema) GetID() string

func (*Schema) GetUnknown

func (s *Schema) GetUnknown(name string) (*Value, error)

func (Schema) IsDraft4

func (s Schema) IsDraft4() bool

func (Schema) IsDraft6

func (s Schema) IsDraft6() bool

func (Schema) IsDraft7

func (s Schema) IsDraft7() bool

func (Schema) IsEmpty

func (s Schema) IsEmpty() bool

Checks if everything is nil and thereby an empty schema, similar to a "true" schema TODO: Update with 20xx-xx props

func (Schema) MarshalJSON

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

func (*Schema) Parse

func (s *Schema) Parse(jsonSchema []byte) (*Schema, error)

func (Schema) Pretty

func (s Schema) Pretty() string

func (*Schema) ResolveRef

func (s *Schema) ResolveRef(ref *Ref) (*Schema, error)

func (*Schema) SetCircularRefThresHold

func (s *Schema) SetCircularRefThresHold(threshold int)

func (*Schema) SetID

func (s *Schema) SetID(id string)

func (*Schema) SetUnknown

func (s *Schema) SetUnknown(name string, val *Value) error

func (Schema) String

func (s Schema) String() string

func (*Schema) UnmarshalJSON

func (s *Schema) UnmarshalJSON(schema []byte) error

func (*Schema) Validate

func (s *Schema) Validate(jsonDoc []byte) (bool, error)

Validate will return on the first encounter of something invalid

type SchemaProp

type SchemaProp uint8
const (
	PropSchema SchemaProp = iota
	PropID
	PropIDDraft04
	PropRef
	PropComment
	PropTitle
	PropDescription
	PropType
	PropEnum
	PropDefault
	PropConst
	PropExamples
	PropReadOnly
	PropWriteOnly
	PropDefinitions
	PropIf
	PropThen
	PropElse
	PropAllOf
	PropAnyOf
	PropOneOf
	PropNot
	PropContentEncoding
	PropContentMediaType
	PropProperties
	PropRequired
	PropMaxProperties
	PropMinProperties
	PropDependencies
	PropPatternProperties
	PropAdditionalProperties
	PropPropertyNames
	PropItems
	PropMaxItems
	PropMinItems
	PropUniqueItems
	PropAdditionalItems
	PropContains
	PropMaxLength
	PropMinLength
	PropFormat
	PropPattern
	PropMultipleOf
	PropMaximum
	PropExclusiveMaximum
	PropMinimum
	PropExclusiveMinimum
)

TODO: Update with 20xx-xx props

type Schemas

type Schemas []*Schema

func NewSubSchemas

func NewSubSchemas(jsonVal []byte, vt jsonparser.ValueType, parentSchema *Schema) (*Schemas, error)

func (Schemas) MarshalJSON

func (s Schemas) MarshalJSON() ([]byte, error)

type Strings

type Strings []*string

func NewStrings

func NewStrings(jsonVal []byte, vt jsonparser.ValueType) (*Strings, error)

func (Strings) MarshalJSON

func (p Strings) MarshalJSON() ([]byte, error)

type Type

type Type struct {
	String  *string
	Strings *[]*string
}

func NewType

func NewType(jsonVal []byte, vt jsonparser.ValueType) (*Type, error)

func (*Type) Has

func (t *Type) Has(vt ValueType) bool

func (Type) MarshalJSON

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

type Value

type Value struct {
	String  *string
	Number  *big.Float
	Boolean *bool
	Null    *bool
	Object  *map[string]*Value
	Array   *[]*Value
	// contains filtered or unexported fields
}

func NewValue

func NewValue(jsonVal []byte, vt jsonparser.ValueType) (*Value, error)

func (*Value) Equal

func (v *Value) Equal(val *Value) bool

func (Value) MarshalJSON

func (v Value) MarshalJSON() ([]byte, error)

func (Value) Raw

func (v Value) Raw() []byte

type ValueType

type ValueType uint8
const (
	NotExist ValueType = iota
	String
	Number
	Object
	Array
	Boolean
	Null
	Unknown
	Integer
)

These are the same as jsonparser.ValueType - except Integer, which jsonparser does not have

func DetectJSONType

func DetectJSONType(data []byte) ValueType

This should more or less match what jsonparser detects. Unfortunately jsonparser doesn't expose it's detect type function.

func (ValueType) ParserValueType

func (v ValueType) ParserValueType() jsonparser.ValueType

func (ValueType) String

func (v ValueType) String() string

type Values

type Values []*Value

func NewValues

func NewValues(jsonVal []byte, vt jsonparser.ValueType) (*Values, error)

func (Values) MarshalJSON

func (v Values) MarshalJSON() ([]byte, error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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