A TypeScript Zod v4-inspired validation library for Go with strict type semantics, fluent schemas, struct tags, and JSON Schema Draft 2020-12 interoperability
- Strict type semantics: Value schemas and pointer schemas accept exact input types unless coercion is explicit.
- Dual parsing modes: Use
Parse(any)for dynamic data andStrictParse(T)for known Go values. - Fluent schema API: Compose primitives, collections, structs, unions, intersections, transforms, refinements, defaults, and metadata.
- Struct tags: Build schemas from Go structs with
gozod:"..."tags, json/yaml/toml field names, custom tag keys, and circular-reference support. - Generated schemas: Use
gozodgenfor tag-heavy paths where generated helpers are preferable to reflection. - Localized errors: Inspect
*gozod.ZodError, prettify or flatten failures, and switch message bundles throughlocales/. - JSON Schema bridge: Convert GoZod schemas to JSON Schema and import JSON Schema back into GoZod with explicit lossy-conversion controls.
- Focused dependency surface: Uses JSON v2,
jsonschema,deepclone, JWT parsing, and Unicode helpers without a framework stack.
go get github.com/kaptinlin/gozodRequires Go 1.26.4+.
package main
import (
"fmt"
"log"
"github.com/kaptinlin/gozod"
)
func main() {
email := gozod.String().Min(5).Email()
value, err := email.Parse("dev@example.com")
if err != nil {
log.Fatal(err)
}
fmt.Println(value)
}Use Parse(any) at boundaries where input is dynamic. Use StrictParse(T) when your program already has the target Go type.
name := gozod.String().Min(2).Max(50)
fromJSON, err := name.Parse("Alice")
if err != nil {
log.Fatal(err)
}
knownValue, err := name.StrictParse("Grace")
if err != nil {
log.Fatal(err)
}
fmt.Println(fromJSON, knownValue)StrictParse keeps the call site compile-time constrained. Parse keeps the boundary flexible and returns ordinary Go errors.
Use FromStruct[T]() when validation belongs next to a Go struct.
package main
import (
"fmt"
"log"
"github.com/kaptinlin/gozod"
)
type User struct {
Name string `json:"name" gozod:"required,min=2,max=50"`
Email string `json:"email" gozod:"required,email"`
Age int `json:"age" gozod:"min=18,max=120"`
}
func main() {
schema := gozod.FromStruct[User]()
user, err := schema.Parse(User{
Name: "Ada Lovelace",
Email: "ada@example.com",
Age: 36,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", user)
}Use gozod.WithTagName("validate") when your project uses another rule tag, and gozod.WithFieldNameTag("yaml") to resolve field names (error paths and JSON Schema) from yaml/toml tags instead of json. See docs/tags.md for supported tag rules and generated-schema details.
Use root constructors when you want the schema shape in code.
user := gozod.Object(gozod.ObjectSchema{
"name": gozod.String().Min(2),
"email": gozod.Email(),
"age": gozod.Int().Min(18),
})
contact := gozod.Union([]any{
gozod.Email(),
gozod.URL(),
})
parsedUser, err := user.Parse(map[string]any{
"name": "Grace",
"email": "grace@example.com",
"age": 28,
})
if err != nil {
log.Fatal(err)
}
parsedContact, err := contact.Parse("https://example.com")
if err != nil {
log.Fatal(err)
}
fmt.Println(parsedUser, parsedContact)For conversion-first flows, import coerce/ and choose coercion explicitly.
import "github.com/kaptinlin/gozod/coerce"
age, err := coerce.Int().Parse("42")
if err != nil {
log.Fatal(err)
}
fmt.Println(age)Default short-circuits validation for nil input. Prefault runs the fallback through the full parsing pipeline.
displayName := gozod.String().Min(3).Default("Guest")
normalized := gozod.String().
Trim().
ToLowerCase().
Prefault(" Example ")
name, _ := displayName.Parse(nil)
slug, _ := normalized.Parse(nil)
fmt.Println(name, slug)Metadata modifiers are copy-on-write. The original schema is left unchanged and metadata is registered on the returned schema.
email := gozod.Email().Meta(gozod.GlobalMeta{
Title: "Email Address",
Description: "Primary contact email",
Examples: []any{"user@example.com"},
})
meta, ok := gozod.GlobalRegistry.Get(email)
fmt.Println(ok, meta.Title)See docs/metadata.md for registries and JSON Schema metadata merging.
gozod.ToJSONSchema returns a *jsonschema.Schema from github.com/kaptinlin/jsonschema.
schema := gozod.Object(gozod.ObjectSchema{
"name": gozod.String().Min(1),
"age": gozod.Int().Min(0),
})
jsonSchema, err := gozod.ToJSONSchema(schema)
if err != nil {
log.Fatal(err)
}
result := jsonSchema.Validate(map[string]any{
"name": "Lin",
"age": 30,
})
fmt.Println(result.IsValid())gozod.FromJSONSchema fails closed on unsupported JSON Schema keywords. Use AllowLossy only when dropping unsupported keywords is intentional.
var ignored []string
zodSchema, err := gozod.FromJSONSchema(jsonSchema, gozod.FromJSONSchemaOptions{
AllowLossy: true,
LossyKeywords: &ignored,
})
if err != nil {
log.Fatal(err)
}
_, _ = zodSchema.ParseAny(map[string]any{"name": "Lin", "age": 30})
fmt.Println(ignored)See docs/json-schema.md for conversion options, unsupported features, registries, and Draft 2020-12 notes.
Validation failures return error. Use GoZod helpers when you need structured inspection or presentation.
schema := gozod.String().Min(5)
_, err := schema.Parse("hi")
if err == nil {
return
}
var zodErr *gozod.ZodError
if gozod.IsZodError(err, &zodErr) {
fmt.Println(gozod.PrettifyError(zodErr))
}See docs/error-customization.md and docs/error-formatting.md.
Install gozodgen when generated struct-tag schemas fit your path better than reflection:
go install github.com/kaptinlin/gozod/cmd/gozodgen@latest
go generate ./...See cmd/gozodgen and examples/code_generation.
- docs/basics.md - core concepts and common patterns
- docs/api.md - API reference and method surface
- docs/tags.md - struct-tag validation guide
- docs/json-schema.md - JSON Schema conversion
- docs/metadata.md - schema metadata and registries
- docs/feature-mapping.md - TypeScript Zod v4 to GoZod mapping
- examples/README.md - runnable examples by topic
Run examples directly:
go run ./examples/quickstart
go run ./examples/struct_tags
go run ./examples/error_handlingGoZod includes benchmarks for parsing, checks, tags, transforms, and configuration helpers.
- Prefer
StrictParsewhen the input type is already known. - Use coerce/ only when conversion is part of the requirement.
- Use
gozodgenfor tag-heavy hot paths where generated helpers are worth the extra file.
go test -bench=. ./...task test # Run go test -race ./...
task test:race # Run race tests for core and lightweight utility packages
task lint # Run golangci-lint and tidy-lint
task golangci-lint # Run golangci-lint v2 only
task tidy-lint # Verify go.mod and go.sum stay tidy
task verify # Run deps, fmt, vet, lint, test, and vuln
go test -tags=contractcheck ./types # Audit compile-time schema contractsFor development guidelines and repository conventions, see AGENTS.md.
Contributions are welcome. Run the test and lint commands before opening a pull request, and keep docs and examples aligned with the current API surface.
This project is licensed under the MIT License - see the LICENSE file for details.