golang implementation of the JSON Schema Specification, which lets you write JSON that validates some other json. Rad.
- Encode schemas back to JSON
- Supply Your own Custom Validators
- Uses Standard Go idioms
- Fastest Go implementation of JSON Schema validators (draft2019_9 only, (old - draft 7) benchmarks are here - thanks @TheWildBlue!)
We would love involvement from more people! If you notice any errors or would like to submit changes, please see our Contributing Guidelines.
We've set up a separate document for developer guidelines!
Here's a quick example pulled from the godoc:
package main
import (
"encoding/json"
"fmt"
"github.com/qri-io/jsonschema"
)
func main() {
var schemaData = []byte(`{
"$id": "https://qri.io/schema/",
"$comment" : "sample comment",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0
},
"friends": {
"type" : "array",
"items" : { "title" : "REFERENCE", "$ref" : "#" }
}
},
"required": ["firstName", "lastName"]
}`)
rs := &Schema{}
if err := json.Unmarshal(schemaData, rs); err != nil {
panic("unmarshal schema: " + err.Error())
}
var valid = []byte(`{
"firstName" : "George",
"lastName" : "Michael"
}`)
errs, err := rs.ValidateBytes(valid)
if err != nil {
panic(err)
}
if len(errs) > 0 {
fmt.Println(errs[0].Error())
}
var invalidPerson = []byte(`{
"firstName" : "Prince"
}`)
errs, err = rs.ValidateBytes(invalidPerson)
if err != nil {
panic(err)
}
if len(errs) > 0 {
fmt.Println(errs[0].Error())
}
var invalidFriend = []byte(`{
"firstName" : "Jay",
"lastName" : "Z",
"friends" : [{
"firstName" : "Nas"
}]
}`)
errs, err = rs.ValidateBytes(invalidFriend)
if err != nil {
panic(err)
}
if len(errs) > 0 {
fmt.Println(errs[0].Error())
}
}
The godoc gives an example of how to supply your own validators to extend the standard keywords supported by the spec.
It involves three steps that should happen before allocating any Schema instances that use the validator:
- create a custom type that implements the
Keyword
interface - Load the appropriate draft keyword set (see
draft2019_09_keywords.go
) - call RegisterKeyword with the keyword you'd like to detect in JSON, and a
KeyMaker
function.
package main
import (
"encoding/json"
"fmt"
"github.com/qri-io/jsonschema"
)
// your custom validator
type IsFoo bool
// newIsFoo is a jsonschama.KeyMaker
func newIsFoo() Keyword {
return new(IsFoo)
}
// Validate implements jsonschema.Keyword
func (f *IsFoo) Validate(propPath string, data interface{}, errs *[]KeyError) {}
// Register implements jsonschema.Keyword
func (f *IsFoo) Register(uri string, registry *SchemaRegistry) {}
// Resolve implements jsonschema.Keyword
func (f *IsFoo) Resolve(pointer jptr.Pointer, uri string) *Schema {
return nil
}
// ValidateKeyword implements jsonschema.Keyword
func (f *IsFoo) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
if str, ok := data.(string); ok {
if str != "foo" {
currentState.AddError(data, fmt.Sprintf("should be foo. plz make '%s' == foo. plz", str))
}
}
}
func main() {
// register a custom validator by supplying a function
// that creates new instances of your Validator.
jsonschema.RegisterKeyword("foo", newIsFoo)
schBytes := []byte(`{ "foo": true }`)
rs := new(Schema)
if err := json.Unmarshal(schBytes, rs); err != nil {
// Real programs handle errors.
panic(err)
}
errs, err := rs.ValidateBytes([]byte(`"bar"`))
if err != nil {
panic(err)
}
fmt.Println(errs[0].Error())
// Output: /: "bar" should be foo. plz make 'bar' == foo. plz
}