.agents
chore: refresh development submodules
.claude
chore: initialize shared agent skills submodule and symlink
.github
chore(deps): bump actions/checkout from 6 to 7
cmd
refactor(modernize): adopt collection helpers
docs
fix: stabilize schema compilation and format semantics
examples
fix: preserve exact JSON number semantics
i18n
fix: restore public go-i18n dependency
internal
test: strengthen i18n coverage
pkg
refactor(simplify): streamline internal helpers
SPECS
fix: stabilize schema compilation and format semantics
testdata
chore(deps): update go-i18n integration
tests
fix: stabilize schema compilation and format semantics
.editorconfig
chore(deps): upgrade dependencies to latest versions
.gitattributes
first commit
.gitignore
fix: preserve exact JSON number semantics
.gitleaks.toml
chore(maint): sync remote state and restore green checks
.gitmodules
chore: initialize shared agent skills submodule and symlink
.golangci.version
fix: preserve direct schema pattern validation
.golangci.yml
fix: preserve direct schema pattern validation
.yamllint.yml
chore(maint): sync remote state and restore green checks
additional_properties.go
fix: preserve direct schema pattern validation
AGENTS.md
docs: refresh library docs
all_of.go
refactor(simplify): flatten validation and schema helpers
any_of.go
refactor(simplify): flatten validation and schema helpers
CLAUDE.md
fix: stabilize schema compilation and format semantics
compilation_concurrency_test.go
fix: stabilize schema compilation and format semantics
compilation_lifecycle_test.go
fix: stabilize schema compilation and format semantics
compilation.go
fix: stabilize schema compilation and format semantics
compiler_test.go
fix: stabilize schema compilation and format semantics
compiler.go
fix: stabilize schema compilation and format semantics
conditional.go
refactor(simplify): trim redundant validation helpers
const.go
feat: expose structured const and enum values
constructor_lifecycle_bench_test.go
fix: stabilize schema compilation and format semantics
constructor_test.go
fix: stabilize schema compilation and format semantics
constructor.go
fix: stabilize schema compilation and format semantics
contains.go
chore(config): align lint baseline and restore green checks
content.go
chore(config): align lint baseline and restore green checks
CONTRIBUTING.md
first commit
default_funcs_test.go
refactor(modernize): iterate split fields lazily
default_funcs.go
refactor(modernize): iterate split fields lazily
dependent_required.go
feat: migrate to standard library JSON v2
dependent_schemas.go
refactor(naming): normalize keyword file names
dialect_extra_test.go
fix: enforce custom dialect vocabularies
dialect.go
fix: stabilize schema compilation and format semantics
doc.go
feat: add multi-draft dialect support with optimized schema decoding
embedding_test.go
feat: migrate to standard library JSON v2
enum.go
feat: expose structured const and enum values
errors.go
fix: stabilize schema compilation and format semantics
exclusive_maximum.go
refactor(naming): normalize keyword file names
exclusive_minimum.go
refactor(naming): normalize keyword file names
format.go
fix: stabilize schema compilation and format semantics
formats.go
fix: stabilize schema compilation and format semantics
go.mod
fix: stabilize schema compilation and format semantics
go.sum
fix: stabilize schema compilation and format semantics
items.go
refactor: simplify validation helpers
keywords_test.go
test: cover schema validation edges
keywords.go
fix: stabilize schema compilation and format semantics
lefthook.yml
chore: drop markdownlint tooling
LICENSE
chore(deps): upgrade dependencies to latest versions
max_items.go
refactor(naming): normalize keyword file names
max_length.go
refactor(naming): normalize keyword file names
max_properties.go
refactor(naming): normalize keyword file names
maximum.go
style: apply Google Go style guide improvements
metaschema.go
fix: stabilize schema compilation and format semantics
min_items.go
refactor(naming): normalize keyword file names
min_length.go
refactor(naming): normalize keyword file names
min_properties.go
refactor(naming): normalize keyword file names
minimum.go
style: apply Google Go style guide improvements
multiple_of.go
refactor(naming): normalize keyword file names
not_test.go
fix(schema): add backward compatibility for 'definitions' keyword
not.go
chore(config): align lint baseline and restore green checks
omitzero_test.go
feat: migrate to standard library JSON v2
one_of.go
refactor(simplify): flatten validation and schema helpers
pattern_properties.go
fix: preserve direct schema pattern validation
pattern.go
fix: make compiled schema validation concurrent safe
performance_bench_test.go
refactor(modernize): adopt current Go stdlib helpers
prefix_items.go
refactor(naming): normalize keyword file names
properties.go
refactor(validation): remove redundant property helpers
property_names.go
refactor(simplify): flatten validation and schema helpers
rat_test.go
fix: preserve exact JSON number semantics
rat.go
feat: migrate to standard library JSON v2
README.md
fix: stabilize schema compilation and format semantics
ref_test.go
fix: stabilize schema compilation and format semantics
ref.go
fix: stabilize schema compilation and format semantics
required.go
style: apply Google Go style guide improvements
result_test.go
feat: migrate to standard library JSON v2
result.go
fix: stabilize schema compilation and format semantics
roundtrip_test.go
feat: migrate to standard library JSON v2
schema_decode.go
feat: migrate to standard library JSON v2
schema_test.go
fix: stabilize schema compilation and format semantics
schema.go
fix: stabilize schema compilation and format semantics
struct_array_validation_test.go
fix(validation): handle nil slice as null in struct validation
struct_tags_test.go
fix: stabilize schema compilation and format semantics
struct_tags.go
fix: stabilize schema compilation and format semantics
struct_validation_test.go
feat: migrate to standard library JSON v2
struct_validation.go
fix: preserve direct schema pattern validation
Taskfile.yml
chore(deps): update go-i18n integration
type.go
style: apply Google Go style guide improvements
unevaluated_items.go
refactor(naming): normalize keyword file names
unevaluated_properties.go
refactor(naming): normalize keyword file names
unique_items_bench_test.go
refactor(naming): normalize keyword file names
unique_items_property_test.go
fix: preserve exact JSON number semantics
unique_items.go
fix: preserve exact JSON number semantics
unmarshal_test.go
fix: preserve exact JSON number semantics
unmarshal.go
fix: preserve exact JSON number semantics
upgrade_taskfile.md
chore(deps): update Go toolchain and libraries
utils_test.go
fix: stabilize schema compilation and format semantics
utils.go
fix: preserve exact JSON number semantics
validate_bench_test.go
refactor(bench): modernize benchmarks to use testing.B.Loop()
validate_test.go
fix: stabilize schema compilation and format semantics
validate.go
fix: stabilize schema compilation and format semantics
validation_plural_errors_test.go
test: cover struct validation error paths

jsonschema

A high-performance JSON Schema validator for Go with direct struct validation, default-aware unmarshaling, a fluent constructor API, and support for Draft 2020-12, Draft 2019-09, Draft-07, Draft-06, and Draft-04.

Features

  • Multiple dialects: Validate Draft 2020-12, Draft 2019-09, Draft-07, Draft-06, and Draft-04 schemas.
  • One main entry point: schema.Validate(input) accepts raw JSON, maps, or Go structs.
  • Exact JSON numbers: Untyped JSON numbers remain encoding/json.Number instead of being rounded through float64.
  • Defaults without surprises: schema.Unmarshal applies 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: Pure validation has zero Intl dependencies; opt into localization by importing the i18n subpackage, or implement the one-method Translator interface yourself.

Installation

go get github.com/kaptinlin/jsonschema

Requires the Go version declared in go.mod.

Quick Start

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)
}

Validation

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"})

Fast paths

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.

MethodUse 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

Exact JSON numbers

The package's default JSON codec preserves every untyped number as encoding/json.Number. Numeric constraints, enum, const, uniqueItems, and numeric format callbacks compare values mathematically, including integers beyond the exact range of float64.

import stdjson "encoding/json"

numberSchema := jsonschema.Number()
var value any
if err := numberSchema.Unmarshal(&value, []byte(`18446744073709551615`)); err != nil {
	log.Fatal(err)
}
number := value.(stdjson.Number)
fmt.Println(number.String()) // 18446744073709551615

Direct Go inputs retain their native numeric types. Use jsonschema.NewRat in numeric custom formats when the callback must accept both native Go numbers and encoding/json.Number. A codec installed with WithDecoderJSON or WithEncoderJSON replaces the corresponding default behavior, so the caller owns its dynamic number types and precision guarantees.

When passing untyped values to a non-JSON serializer, adapt encoding/json.Number at that serializer boundary. Do not convert through float64; emit an exact target-format number or return an error.

Dialect Support

Schemas are compiled using the dialect declared by $schema. When $schema is absent, the compiler defaults to Draft 2020-12.

compiler := jsonschema.NewCompiler()
compiler.SetDefaultDialect(jsonschema.Draft7) // for schemas without "$schema"
schema, err := compiler.Compile(schemaBytes)

Supported dialects are:

DialectConstant
Draft 2020-12jsonschema.Draft202012
Draft 2019-09jsonschema.Draft201909
Draft-07jsonschema.Draft7
Draft-06jsonschema.Draft6
Draft-04jsonschema.Draft4

format remains annotation-only in the standard Draft 2020-12 dialect. Custom dialects can require Format-Assertion, and callers can independently enable best-effort assertion with SetAssertFormat(true). Compile does not perform schema meta-validation by default; call ValidateSchema when the schema document itself is untrusted.

result, err := compiler.ValidateSchema(schemaBytes)
if err != nil {
	log.Fatal(err)
}
if !result.IsValid() {
	log.Fatalf("invalid schema document: %v", result.Errors)
}

See docs/dialects.md for compatibility details.

Constructor API

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(...).

Struct Tags and Code Generation

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
schemagen

Use FromStructWithOptions when you need a custom tag name, schema version, required-field ordering, or schema-level properties.

Advanced Features

Custom Formats

Format validation is annotation-only by default. Enable best-effort assertion explicitly and register your own validators when application policy requires it. Schemas using a custom Format-Assertion dialect assert formats automatically.

compiler := jsonschema.NewCompiler().SetAssertFormat(true)
compiler.RegisterFormat("customer-id", func(v any) bool {
	s, ok := v.(string)
	return ok && strings.HasPrefix(s, "CUST-")
}, "string")

Dynamic Defaults

Register default functions on a compiler, then unmarshal validated data through schemas that use those defaults.

compiler := jsonschema.NewCompiler()
compiler.RegisterDefaultFunc("now", jsonschema.DefaultNowFunc)

References, Extras, and Batch Compilation

  • Use CompileBatch for interdependent resources. Compilation rejects missing references and duplicate resource definitions; use Compiler.Schema to reuse a published definition. Concurrent loading of a shared dependency reuses the first published resource. Later compilation never rebinds an existing graph.
  • Reuse compiled nodes directly in constructor composition to preserve their resource scope and compiler policy. For repeated validation of a standalone constructor document, use MarshalJSON followed by Compile once; direct validation of mutable builders checks the entire graph for unresolved references.
  • Use SetPreserveExtra(true) when tools need to keep non-standard extension keywords in Schema.Extra.
  • Reference loaders are pluggable per scheme via RegisterLoader. http and https ship pre-registered with a 10s timeout. If your schemas come from untrusted sources, replace or remove those loaders so external $ref resolution is gated by your own host/size/timeout policy.

Localized Results

The root package knows nothing about translation frameworks — pure validation users pay zero Intl compile, link, and binary cost. Localization lives in the optional i18n subpackage:

import "github.com/kaptinlin/jsonschema/i18n"

zh, err := i18n.New("zh-Hans") // one translator per locale
if err != nil {
	log.Fatal(err)
}

localized := result.ToLocalizedList(zh)
fmt.Println(localized.Valid)

for path, message := range result.LocalizedDetailedErrors(zh) {
	fmt.Printf("%s: %s\n", path, message)
}

Built-in locales: en, de-DE, es-ES, fr-FR, ja-JP, ko-KR, pt-BR, zh-Hans, zh-Hant. Missing translations fall back to the built-in English message; localization never fails.

Don't want go-i18n? Implement the one-method jsonschema.Translator interface with any backend — a database, gettext, or a hardcoded map.

Error Handling

  • Compilation failures return regular Go errors, including sentinel errors such as ErrRegexValidation.
  • Structured error types such as RegexPatternError, StructTagError, and UnmarshalError work with errors.As.
  • Validation failures are returned in *EvaluationResult; use IsValid, Errors, ToFlag, ToList, or ToLocalizedList depending on how much detail you need.

Documentation

Development

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 govulncheck

For development conventions and agent-facing project rules, see CLAUDE.md.

Contributing

Contributions are welcome. Open an issue for large API or behavior changes before sending a pull request.

License

This project is licensed under the MIT License - see the LICENSE file for details.