README
¶
go-z
Schema-first validation for Go. Define a schema once, parse anything into it, and get structured errors back — with the ergonomics of a fluent schema API and the performance of hand-written Go.
Inspired by Zod, not affiliated with it. go-z is an independent project, not endorsed by or sponsored by Zod or its authors. Portions are derived from Zod under the MIT licence — see NOTICE.
Documentation · Quickstart · API reference · Benchmarks
Install
go get github.com/iKunalChhabra/go-z/z
Requires Go 1.26+. Zero dependencies for the core package; the optional
zgin subpackage pulls in Gin.
Quick start
package main
import (
"fmt"
"github.com/iKunalChhabra/go-z/z"
)
func main {
user:= z.Object(z.Shape{
"name": z.String.Min(2).Max(100),
"email": z.String.Email,
"age": z.Int.Gte(0).Lt(150).Optional,
})
data, err:= user.Parse(map[string]any{
"name": "Ada",
"email": "ada@example.com",
})
if err != nil {
zerr, _:= z.AsError(err)
fmt.Println(z.Prettify(zerr))
return
}
fmt.Println(data) // map[email:ada@example.com name:Ada]
}
The package is named z and lives at /z, so a plain import already reads the
way you would want — no alias required:
import "github.com/iKunalChhabra/go-z/z"
z.String.Min(5).Email
Features
- Fluent schema API.
z.String.Min(5).Email,z.Object(z.Shape{…}),Optional/Nullable/Default/Catch/Pipe/Transform/Refine. - Typed edges.
String.Optional.Parse(v)returns(*string, error);String.Default("x").Parse(v)returns(string, error). Generic wrappers keep the inner type instead of collapsing toany. - Structured errors. Eleven issue codes with paths and stable JSON field
names, plus
Flatten/Format/Treeify/Prettify. - Bidirectional codecs.
z.Decode/z.Encodewith direction-aware defaults. - JSON Schema export.
z.ToJSONSchemafor OpenAPI and client-side validators. - i18n. Error maps and seven locales (
en es fr de ja pt zh). - Concurrency-safe. Schemas are immutable after construction;
Parseis lock-free and-raceclean.ParseParallelSlicefans large slices across cores. - Gin integration.
zgin.Validate,zgin.BindJSON, typedzgin.GetAs[T].
Usage
Safe parsing
res:= z.String.Email.SafeParse("nope")
if !res.Success {
fmt.Println(res.Error.Issues[0].Code) // invalid_format
}
Objects, unions, recursion
var Category z.AnySchemaLike
Category = z.Lazy(func z.AnySchemaLike {
return z.Object(z.Shape{
"name": z.String.Min(1),
"children": z.Array(Category).Default([]any{}),
})
})
userOrGuest:= z.DiscriminatedUnion("role", []z.AnySchemaLike{
z.Object(z.Shape{"role": z.Literal("admin"), "perms": z.Array(z.String)}),
z.Object(z.Shape{"role": z.Literal("guest"), "session": z.String.UUID}),
})
Structs
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
parsed, err:= z.ToStruct[User](user).Parse(input) // parsed is a User
Codecs
isoDate:= z.Codec(z.String.ISODateTime, z.Time, z.CodecTx{
Decode: func(v any, _ *z.RefinementCtx) (any, error) {
return time.Parse(time.RFC3339Nano, v.(string))
},
Encode: func(v any, _ *z.RefinementCtx) (any, error) {
return v.(time.Time).UTC.Format(time.RFC3339Nano), nil
},
})
t, _:= z.Decode(isoDate, "2024-01-15T10:30:00Z") // time.Time
s, _:= z.Encode(isoDate, t) // ISO string
Gin
import "github.com/iKunalChhabra/go-z/zgin"
r.POST("/users", zgin.Validate(user), func(c *gin.Context) {
body, _:= zgin.Get(c) // already parsed and validated
c.JSON(200, body)
})
Failed validation writes structured issues automatically:
{"success":false,"error":{"issues":[{"code":"too_small","path":["name"],"message":"Too small: expected string to have >=2 characters"}]}}
Flatten, Treeify, and Prettify renderers are available via zgin.Options.
Concurrency
schema:= z.String.Email // build once, share freely
go func { schema.Parse(a) }
go func { schema.Parse(b) }
out, err:= z.ParseParallelSlice(ctx, itemSchema, items, z.ParallelOpts{})
Performance
4-core Xeon, Go 1.26, median of six runs. Full methodology and tables in BENCHMARKS.md.
| Scenario | go-z | go-playground/validator | Oudwins/zog |
|---|---|---|---|
| Flat object | 416 ns | 607 ns | 1295 ns |
| Nested object | 978 ns | 1090 ns | 2783 ns |
| String formats (email + uuid + url) | 890 ns | 1099 ns | 1810 ns |
| Array of 10k (parallel) | 2.75 ms | 6.09 ms | 12.9 ms |
Email, UUID, and the ISO date/time formats use hand-written matchers rather than backtracking regexes; each is differential-tested against the regex it replaced over hundreds of thousands of random inputs. Building structured issues makes the failure path slower than tag-based validators — see BENCHMARKS.md.
Design notes
- Untyped core, typed edge. The engine runs on
any;Schema[T]is the generic boundary.OptionalandNullableyield*T(nil means absent or null); every other wrapper yieldsT. Each wrapper has a type-erased constructor for heterogeneous containers (Optional(anySchema)) and a typed one (OptionalOf,DefaultOf,RefineOf, …) that works with every schema type. - JSON model first. Objects produce
map[string]any, arrays[]any, andInt/Numberproducefloat64. UseInt64for a typedint64edge orToStruct[T]for structs. Missingis notnil.Missingmeans an absent key (JSundefined);nilis JSONnull.Optionalaccepts Missing,Nullableaccepts nil,Nullishboth.- Params are checked at definition time. An unsupported params type panics while the schema is built — at startup, never during request handling.
- Object field order.
Object(Shape)reports issues in sorted key order because Go maps are unordered;ObjectOrdered([]Field{…})preserves definition order.
Project layout
Every package is a directory; none of them is special.
z/ core package — import "github.com/iKunalChhabra/go-z/z"
schema_*.go schema types (string, number, object, union, codec, …)
checks_*.go composable checks
fluent*.go mid-chain Optional/Default/Refine on concrete schemas
matchers.go hand-written format matchers
errorutils.go Flatten / Format / Treeify / Prettify
jsonschema.go ToJSONSchema
locale_*.go i18n error maps
parallel.go ParseParallelSlice
tostruct.go cached reflect decode
zgin/ Gin binding and middleware
bench/ comparative benchmarks (separate module)
docs/ documentation site
Status
Implemented: primitives and string formats, objects and collections, unions / xor /
discriminated unions / intersection / lazy, wrappers, codecs, ToJSONSchema,
template literals, coercion, error utilities, seven locales, Gin, struct binding,
and parallel parsing. Behavioural parity is tracked in z/parity_*_test.go — see
PARITY.md.
Not implemented: fromJSONSchema, and the JavaScript-only surface (z.function,
z.promise, z.symbol, z.file). Async parsing is unnecessary in Go.
Contributing
Issues and pull requests are welcome. Before submitting:
go test -race./...
go vet./...
gofmt -l.
Author
Kunal Chhabra (@iKunalChhabra)
Licence and attribution
MIT — Copyright (c) 2026 Kunal Chhabra. See LICENSE.
Portions of this project are derived from Zod (MIT, Copyright (c) 2025 Colin McDonnell): string-format patterns, locale message text, the issue taxonomy, and behavioural test cases ported from Zod's test suite. Zod's licence is reproduced in full in NOTICE.