A high-performance JSON Schema Draft 2020-12 validator for Go with direct struct validation, default-aware unmarshaling, and a fluent constructor API
- Draft 2020-12: Validate schemas and instances against the current JSON Schema specification.
- One main entry point:
schema.Validate(input)accepts raw JSON, maps, or Go structs. - Defaults without surprises:
schema.Unmarshalapplies schema defaults; validation stays a separate step. - Constructor API: Build schemas in Go with
Object,Prop,String,Required, and composition helpers. - Struct tags: Generate schemas from Go types with
FromStruct. - Extensible: Register custom formats, default functions, media types, decoders, and reference loaders on a compiler.
- Localized errors: Render validation output through
go-i18nlocalizers.
go get github.com/kaptinlin/jsonschemaRequires the Go version declared in go.mod.
package main
import (
"fmt"
"log"
"github.com/kaptinlin/jsonschema"
)
type User struct {
Name string `json:"name"`
Country string `json:"country"`
}
func main() {
schema, err := jsonschema.NewCompiler().Compile([]byte(`{
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1},
"country": {"type": "string", "default": "US"}
},
"required": ["name"]
}`))
if err != nil {
log.Fatal(err)
}
data := []byte(`{"name":"Alice"}`)
result := schema.Validate(data)
if !result.IsValid() {
log.Fatalf("invalid payload: %v", result.Errors)
}
var user User
if err := schema.Unmarshal(&user, data); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", user)
}schema.Validate is the main entry point. Pass raw JSON, a decoded map, or a Go struct — it dispatches internally.
result := schema.Validate([]byte(`{"name":"Alice"}`))
result = schema.Validate(map[string]any{"name": "Alice"})
result = schema.Validate(User{Name: "Alice"})When the input type is known and you want to skip the dispatch and any extra conversion, call the type-specific method directly. They share semantics with Validate.
| Method | Use When |
|---|---|
ValidateJSON([]byte) |
Hot paths handling raw JSON request bodies or stored documents |
ValidateMap(map[string]any) |
Already-decoded JSON objects you do not want re-encoded |
ValidateStruct(any) |
Go values, when you want to avoid a JSON round-trip |
Build schemas directly in Go when you want type-safe composition instead of raw JSON literals.
userSchema := jsonschema.Object(
jsonschema.Prop("name", jsonschema.String(jsonschema.MinLength(1))),
jsonschema.Prop("email", jsonschema.Email()),
jsonschema.Prop("age", jsonschema.Integer(jsonschema.Min(0))),
jsonschema.Required("name", "email"),
)
result := userSchema.Validate(map[string]any{
"name": "Alice",
"email": "alice@example.com",
"age": 30,
})
fmt.Println(result.IsValid())The constructor layer also includes composition helpers such as OneOf, AnyOf, AllOf, Not, and If(...).Then(...).Else(...).
Generate schemas from Go types at runtime with FromStruct, or use schemagen for generated code.
type Signup struct {
Name string `jsonschema:"required,minLength=2,maxLength=50"`
Email string `jsonschema:"required,format=email"`
Age int `jsonschema:"minimum=18"`
}
schema, err := jsonschema.FromStruct[Signup]()
if err != nil {
log.Fatal(err)
}
fmt.Println(schema.Validate(map[string]any{
"name": "Alice",
"email": "alice@example.com",
"age": 28,
}).IsValid())Install the generator when you want compile-time helpers:
go install github.com/kaptinlin/jsonschema/cmd/schemagen@latest
schemagenUse FromStructWithOptions when you need a custom tag name, schema version, required-field ordering, or schema-level properties.
Format validation is annotation-only by default. Turn it on explicitly and register your own validators when needed.
compiler := jsonschema.NewCompiler().SetAssertFormat(true)
compiler.RegisterFormat("customer-id", func(v any) bool {
s, ok := v.(string)
return ok && strings.HasPrefix(s, "CUST-")
}, "string")Register default functions on a compiler, then unmarshal validated data through schemas that use those defaults.
compiler := jsonschema.NewCompiler()
compiler.RegisterDefaultFunc("now", jsonschema.DefaultNowFunc)- Use
CompileBatchto compile related schemas before resolving cross-references. - Use
SetPreserveExtra(true)when tools need to keep non-standard extension keywords inSchema.Extra. - Reference loaders are pluggable per scheme via
RegisterLoader.httpandhttpsship pre-registered with a 10s timeout. If your schemas come from untrusted sources, replace or remove those loaders so external$refresolution is gated by your own host/size/timeout policy.
i18nBundle, err := jsonschema.I18n()
if err != nil {
log.Fatal(err)
}
localizer := i18nBundle.NewLocalizer("zh-Hans")
localized := result.ToLocalizeList(localizer)
fmt.Println(localized.Valid)- Compilation failures return regular Go errors, including sentinel errors such as
ErrRegexValidation. - Structured error types such as
RegexPatternError,StructTagError, andUnmarshalErrorwork witherrors.As. - Validation failures are returned in
*EvaluationResult; useIsValid,Errors,ToFlag,ToList, orToLocalizeListdepending on how much detail you need.
- docs/api.md — API reference
- docs/validation.md — validation workflow details
- docs/unmarshal.md — default-aware unmarshaling
- docs/constructor.md — constructor API guide
- docs/tags.md — struct-tag schema generation
- docs/format-validation.md — format behavior and custom validators
- docs/error-handling.md — error patterns
- examples/README.md — runnable examples
task test # Run the full test suite with race detection
task lint # Run golangci-lint and tidy checks
task bench # Run benchmarks
task verify # Run deps, fmt, vet, lint, test, and govulncheckFor development conventions and agent-facing project rules, see AGENTS.md.
Contributions are welcome. Open an issue for large API or behavior changes before sending a pull request.
This project is licensed under the MIT License - see the LICENSE file for details.