# jsonschema-go `jsonschema-go` is a Go implementation of the [JSON Schema specification](https://json-schema.org/), developed by Google. The library provides three core capabilities: constructing JSON Schema objects as Go structs, validating JSON values against a schema, and inferring a schema automatically from a Go type. It supports both the Draft 2020-12 and Draft-07 specifications and has no external dependencies outside the Go standard library. The package is centered on the `Schema` struct, which maps directly to JSON Schema's object model. Schemas may be built programmatically, loaded from JSON via `encoding/json`, or derived from Go structs using the `For`/`ForType` inference functions. Before a schema can validate instances, it must first be resolved (via `Schema.Resolve`) to produce a `Resolved` value—this step validates the schema itself, resolves `$ref`/`$dynamicRef` pointers, and compiles regular expressions. The `Resolved.Validate` method then performs draft-compliant validation against any Go value that represents a JSON document. --- ## API Reference ### `Schema` struct — core JSON Schema object `Schema` is the Go representation of a JSON Schema object, supporting all Draft-07 and Draft 2020-12 keywords. Fields map directly to their JSON counterparts. `Type` and `Types` are mutually exclusive. Unknown keywords are preserved in the `Extra` map to allow round-tripping. ```go package main import ( "encoding/json" "fmt" "github.com/google/jsonschema-go/jsonschema" ) func main() { // Build a schema programmatically minAge := 0.0 maxAge := 150.0 minName := 1 s := &jsonschema.Schema{ Schema: "https://json-schema.org/draft/2020-12/schema", Title: "Person", Description: "A person object", Type: "object", Properties: map[string]*jsonschema.Schema{ "name": { Type: "string", MinLength: jsonschema.Ptr(minName), }, "age": { Type: "integer", Minimum: &minAge, Maximum: &maxAge, }, "email": { Type: "string", Pattern: `^[^@]+@[^@]+$`, }, }, Required: []string{"name", "age"}, AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}}, // false } // Serialize to JSON data, err := json.MarshalIndent(s, "", " ") if err != nil { panic(err) } fmt.Println(string(data)) // Output (abbreviated): // { // "$schema": "https://json-schema.org/draft/2020-12/schema", // "title": "Person", // "type": "object", // "properties": { "name": {...}, "age": {...}, "email": {...} }, // "required": ["name","age"], // "additionalProperties": false // } // Load a schema from JSON raw := `{"type":"string","minLength":3,"maxLength":20}` var loaded jsonschema.Schema if err := json.Unmarshal([]byte(raw), &loaded); err != nil { panic(err) } fmt.Println(loaded.Type) // string } ``` --- ### `Schema.Resolve` — resolve references and prepare for validation `Resolve` validates the schema against its meta-schema, resolves all `$ref` and `$dynamicRef` pointers, compiles regular expressions, and optionally validates `default` values. It returns a `*Resolved` ready for use with `Validate`. Pass `nil` opts for defaults (no base URI, no loader, no default validation). ```go package main import ( "encoding/json" "fmt" "net/url" "github.com/google/jsonschema-go/jsonschema" ) func main() { // --- Simple resolution (no external refs) --- s := &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "id": {Type: "integer"}, "label": {Type: "string"}, }, Required: []string{"id"}, } resolved, err := s.Resolve(nil) // nil opts = defaults if err != nil { panic(err) // schema is structurally invalid } _ = resolved // --- Resolution with a custom Loader for remote $refs --- schemaWithRef := &jsonschema.Schema{ Schema: "https://json-schema.org/draft/2020-12/schema", Defs: map[string]*jsonschema.Schema{ "address": { Type: "object", Properties: map[string]*jsonschema.Schema{ "street": {Type: "string"}, "city": {Type: "string"}, }, }, }, Ref: "#/$defs/address", } opts := &jsonschema.ResolveOptions{ BaseURI: "https://example.com/schemas/person.json", // Loader is called when a $ref points to an external URI Loader: func(u *url.URL) (*jsonschema.Schema, error) { // Fetch and unmarshal the schema at u var remote jsonschema.Schema // ... http.Get(u.String()) and json.Unmarshal ... return &remote, nil }, ValidateDefaults: true, // verify "default" values pass schema validation } resolved2, err := schemaWithRef.Resolve(opts) if err != nil { fmt.Println("resolution error:", err) return } fmt.Println("schema:", resolved2.Schema().Title) } ``` --- ### `Resolved.Validate` — validate a JSON value against a resolved schema `Validate` checks any Go value that represents JSON (the result of `json.Unmarshal` into `any`, or a `map[string]any`) against the resolved schema. Returns `nil` on success or a descriptive error on failure. ```go package main import ( "encoding/json" "fmt" "github.com/google/jsonschema-go/jsonschema" ) func main() { // Define and resolve a schema s := &jsonschema.Schema{ Schema: "https://json-schema.org/draft/2020-12/schema", Type: "object", Properties: map[string]*jsonschema.Schema{ "username": {Type: "string", MinLength: jsonschema.Ptr(3)}, "score": {Type: "integer", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(100.0)}, "tags": {Types: []string{"null", "array"}, Items: &jsonschema.Schema{Type: "string"}}, }, Required: []string{"username", "score"}, } resolved, err := s.Resolve(nil) if err != nil { panic(err) } // Valid instance (from JSON unmarshal) goodJSON := `{"username": "alice", "score": 95, "tags": ["go", "schema"]}` var good any json.Unmarshal([]byte(goodJSON), &good) if err := resolved.Validate(good); err != nil { fmt.Println("unexpected error:", err) } else { fmt.Println("valid!") // valid! } // Invalid: score out of range badJSON := `{"username": "bob", "score": 200}` var bad any json.Unmarshal([]byte(badJSON), &bad) if err := resolved.Validate(bad); err != nil { fmt.Println("validation error:", err) // validation error: validating root: maximum: 200 is greater than 100.000000 } // Invalid: missing required field var missingField any json.Unmarshal([]byte(`{"username":"carol"}`), &missingField) if err := resolved.Validate(missingField); err != nil { fmt.Println("missing field error:", err) // missing field error: validating root: required: missing properties: ["score"] } } ``` --- ### `Resolved.ApplyDefaults` — apply schema defaults to an instance map `ApplyDefaults` walks the schema's `properties` and fills in any missing optional properties in the instance with their `default` values. Only works on `map[string]any` instances (not structs). The argument must be a pointer to the instance. ```go package main import ( "encoding/json" "fmt" "github.com/google/jsonschema-go/jsonschema" ) func main() { schemaJSON := `{ "type": "object", "properties": { "host": {"type": "string", "default": "localhost"}, "port": {"type": "integer", "default": 8080}, "debug": {"type": "boolean", "default": false}, "timeout": {"type": "integer"} }, "required": ["timeout"] }` var s jsonschema.Schema if err := json.Unmarshal([]byte(schemaJSON), &s); err != nil { panic(err) } resolved, err := s.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true}) if err != nil { panic(err) } // Instance with only the required field present instance := map[string]any{"timeout": 30} if err := resolved.ApplyDefaults(&instance); err != nil { panic(err) } fmt.Println(instance["host"]) // localhost fmt.Println(instance["port"]) // 8080 fmt.Println(instance["debug"]) // false fmt.Println(instance["timeout"]) // 30 (required; no default applied) // Validate after applying defaults if err := resolved.Validate(instance); err != nil { fmt.Println("error:", err) } else { fmt.Println("valid after defaults applied!") } } ``` --- ### `For[T]` — infer a schema from a Go generic type `For[T]` uses reflection to construct a `*Schema` describing the Go type `T`. JSON tags control property names and optional fields; `jsonschema` tags set descriptions. Pointer types allow `null`. Standard library types like `time.Time` and `big.Int` map to their JSON serialization schemas. ```go package main import ( "encoding/json" "fmt" "time" "github.com/google/jsonschema-go/jsonschema" ) type Address struct { Street string `json:"street" jsonschema:"street address line"` City string `json:"city" jsonschema:"city name"` Zip string `json:"zip" jsonschema:"postal code"` } type User struct { ID int64 `json:"id" jsonschema:"unique user identifier"` Username string `json:"username" jsonschema:"login name, 3-20 chars"` Email *string `json:"email,omitempty" jsonschema:"contact email"` CreatedAt time.Time `json:"created_at" jsonschema:"creation timestamp"` Address Address `json:"address" jsonschema:"mailing address"` Tags []string `json:"tags,omitempty"` Score float64 `json:"score,omitzero"` } func main() { s, err := jsonschema.For[User](nil) if err != nil { panic(err) } data, _ := json.MarshalIndent(s, "", " ") fmt.Println(string(data)) // { // "type": "object", // "properties": { // "id": {"type": "integer", "description": "unique user identifier"}, // "username": {"type": "string", "description": "login name, 3-20 chars"}, // "email": {"type": ["null","string"], "description": "contact email"}, // "created_at": {"type": "string", "description": "creation timestamp"}, // "address": {"type": "object", "properties": {...}, "required": ["street","city","zip"], ...}, // "tags": {"type": ["null","array"], "items": {"type":"string"}}, // "score": {"type": "number"} // }, // "required": ["id","username","created_at","address"], // "additionalProperties": false // } // Resolve and validate resolved, err := s.Resolve(nil) if err != nil { panic(err) } instance := map[string]any{ "id": float64(42), "username": "alice", "created_at": "2024-01-01T00:00:00Z", "address": map[string]any{ "street": "123 Main St", "city": "Springfield", "zip": "12345", }, } if err := resolved.Validate(instance); err != nil { fmt.Println("error:", err) } else { fmt.Println("valid!") } } ``` --- ### `ForType` — infer a schema from a `reflect.Type` `ForType` is the reflection-based variant of `For`, accepting a `reflect.Type` at runtime. It supports the same `ForOptions` including `TypeSchemas` (overrides for specific types) and `IgnoreInvalidTypes` (skip unsupported types like `func` or `chan` rather than returning an error). ```go package main import ( "encoding/json" "fmt" "reflect" "github.com/google/jsonschema-go/jsonschema" ) type Status int type Event struct { Name string Status Status Meta map[string]any Skip func() // will be ignored } func main() { // Provide a custom schema for Status (otherwise it would be "integer") opts := &jsonschema.ForOptions{ IgnoreInvalidTypes: true, // ignore func fields TypeSchemas: map[reflect.Type]*jsonschema.Schema{ reflect.TypeFor[Status](): { Type: "string", Enum: []any{"pending", "active", "closed"}, }, }, } t := reflect.TypeOf(Event{}) s, err := jsonschema.ForType(t, opts) if err != nil { panic(err) } data, _ := json.MarshalIndent(s, "", " ") fmt.Println(string(data)) // { // "type": "object", // "properties": { // "Name": {"type": "string"}, // "Status": {"type": "string", "enum": ["pending","active","closed"]}, // "Meta": {"type": "object", "additionalProperties": {}} // }, // "required": ["Name","Status","Meta"], // "additionalProperties": false // } } ``` --- ### `Schema.CloneSchemas` — deep-copy a schema tree `CloneSchemas` returns a deep copy of a `Schema`, recursively cloning all sub-schemas. The clone is independent of the original: sub-schemas may be shared as fields of different parent schemas without violating the tree requirement imposed by `Resolve`. ```go package main import ( "encoding/json" "fmt" "github.com/google/jsonschema-go/jsonschema" ) func main() { base := &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "id": {Type: "integer"}, "name": {Type: "string"}, }, Required: []string{"id"}, } // Clone and extend without affecting the original extended := base.CloneSchemas() extended.Properties["email"] = &jsonschema.Schema{Type: "string"} extended.Required = append(extended.Required, "name") origData, _ := json.Marshal(base) extData, _ := json.Marshal(extended) fmt.Println("original:", string(origData)) // original: {"type":"object","properties":{"id":...,"name":...},"required":["id"]} fmt.Println("extended:", string(extData)) // extended: {"type":"object","properties":{"id":...,"name":...,"email":...},"required":["id","name"]} // Use the same base in an allOf without sharing pointers (required by Resolve) parent := &jsonschema.Schema{ AllOf: []*jsonschema.Schema{ base.CloneSchemas(), base.CloneSchemas(), }, } if _, err := parent.Resolve(nil); err != nil { fmt.Println("error:", err) // would error if sub-schemas were shared pointers } else { fmt.Println("resolved successfully") } } ``` --- ### `Equal` — JSON-semantic equality for Go values `Equal` compares two Go values as JSON values according to the JSON Schema specification. Unlike `reflect.DeepEqual`, numbers are compared mathematically (so `int(1)` equals `float64(1.0)`). ```go package main import ( "fmt" "github.com/google/jsonschema-go/jsonschema" ) func main() { // Numeric equality across types fmt.Println(jsonschema.Equal(1, 1.0)) // true (int == float64) fmt.Println(jsonschema.Equal(int8(42), 42.0)) // true fmt.Println(jsonschema.Equal(1, 2)) // false // Structural equality a := map[string]any{"x": 1, "y": []any{true, "hello"}} b := map[string]any{"x": 1.0, "y": []any{true, "hello"}} fmt.Println(jsonschema.Equal(a, b)) // true // Slice equality fmt.Println(jsonschema.Equal([]any{1, 2, 3}, []any{1.0, 2.0, 3.0})) // true fmt.Println(jsonschema.Equal([]any{1, 2}, []any{1, 2, 3})) // false } ``` --- ### `Ptr[T]` — generic pointer helper `Ptr` returns a pointer to a new variable holding the given value. Used when schema fields like `MinLength`, `MaxItems`, `Minimum`, etc., require `*int` or `*float64`. ```go package main import ( "encoding/json" "fmt" "github.com/google/jsonschema-go/jsonschema" ) func main() { s := &jsonschema.Schema{ Type: "string", MinLength: jsonschema.Ptr(5), MaxLength: jsonschema.Ptr(100), } numSchema := &jsonschema.Schema{ Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0), ExclusiveMaximum: jsonschema.Ptr(1.0), MultipleOf: jsonschema.Ptr(0.01), } arrSchema := &jsonschema.Schema{ Types: []string{"null", "array"}, Items: &jsonschema.Schema{Type: "integer"}, MinItems: jsonschema.Ptr(1), MaxItems: jsonschema.Ptr(10), } for _, sc := range []*jsonschema.Schema{s, numSchema, arrSchema} { data, _ := json.Marshal(sc) fmt.Println(string(data)) } // {"type":"string","minLength":5,"maxLength":100} // {"type":"number","minimum":0,"maximum":1,"exclusiveMaximum":1,"multipleOf":0.01} // {"type":["null","array"],"items":{"type":"integer"},"minItems":1,"maxItems":10} } ``` --- ### `Resolved.Schema` — retrieve the original schema from a resolved value `Schema()` returns the root `*Schema` that was resolved. It must not be modified after resolution. ```go package main import ( "fmt" "github.com/google/jsonschema-go/jsonschema" ) func main() { s := &jsonschema.Schema{ Schema: "https://json-schema.org/draft/2020-12/schema", Title: "Config", Type: "object", Properties: map[string]*jsonschema.Schema{ "debug": {Type: "boolean"}, }, } resolved, err := s.Resolve(nil) if err != nil { panic(err) } original := resolved.Schema() fmt.Println(original.Title) // Config fmt.Println(original.Schema) // https://json-schema.org/draft/2020-12/schema } ``` --- ## Summary `jsonschema-go` is well-suited for two primary use cases. The first is **input validation in APIs and services**: unmarshal or programmatically construct a schema once, resolve it, then call `Validate` concurrently against any number of incoming JSON payloads. The library's schema resolution is safe to share across goroutines, and `Validate` accepts plain `map[string]any` values from `json.Unmarshal`, making it trivial to slot into existing HTTP handlers or message processors. `ApplyDefaults` complements validation by filling in missing optional fields before processing, enabling schema-driven configuration patterns. The second major use case is **code generation and schema tooling**: use `For[T]` or `ForType` to derive a JSON Schema from existing Go types—useful for generating OpenAPI specs, documentation, or validation schemas from a canonical Go data model. The `PropertyOrder` field preserves struct field ordering in the serialized JSON, which matters for schema registries and generated documentation. `CloneSchemas` enables safely combining or extending base schemas into composed schemas without pointer aliasing. Round-tripping through `json.Marshal`/`json.Unmarshal` is fully supported, and unknown keywords survive via the `Extra` map, so the library integrates cleanly with schema registries and external toolchains.