go-z

module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: MIT

README

go-z

Test Go Reference

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 · Changelog

Install

go get github.com/iKunalChhabra/go-z/z

Requires Go 1.26+.

The core package has a single dependency, golang.org/x/text, used for Unicode NFC normalisation in Normalize(). Nothing else enters your go.sum. The Gin integration is a separate module, so Gin is only downloaded if you ask for it:

go get github.com/iKunalChhabra/go-z/zgin

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 to any.
  • Structured errors. Eleven issue codes with paths and stable JSON field names, plus Flatten / Format / Treeify / Prettify.
  • Bidirectional codecs. z.Decode / z.Encode with direction-aware defaults.
  • JSON Schema export. z.ToJSONSchema for 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; Parse is lock-free and -race clean. ParseParallelSlice fans large slices across cores.
  • Gin integration. zgin.Validate, zgin.BindJSON, typed zgin.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.5, median of nine runs on a shared cloud VM. Full methodology, spread, and tables in BENCHMARKS.md.

Scenario go-z go-playground/validator Oudwins/zog
Flat object 528 ns 637 ns 1258 ns
Nested object 1184 ns 1112 ns 2646 ns
String formats (email + uuid + url) 798 ns 1088 ns 1713 ns
Array of 10k (parallel) 2.45 ms 6.28 ms 12.5 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. Two places go-z does not win: nested objects, where it is ~6% behind validator because every level costs a child payload and a path segment, and the failure path, which is ~3.6× slower because structured issues are finalized through the error-map chain.

Design notes

  • Untyped core, typed edge. The engine runs on any; Schema[T] is the generic boundary. Optional and Nullable yield *T (nil means absent or null); every other wrapper yields T. 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 and arrays []any; use ToStruct[T] when you want a struct. Numeric schemas produce the Go type they are named after — Int an int, Uint32 a uint32, Number a float64 — converting the incoming JSON number only when the conversion is exact.
  • Missing is not nil. Missing means an absent key (JS undefined); nil is JSON null. Optional accepts Missing, Nullable accepts nil, Nullish both.
  • 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 (separate module)
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.

Directories

Path Synopsis
z
Package z provides schema-first validation for Go: schemas are values you build, compose and share, and parsing returns a typed result or a structured error describing every failure.
Package z provides schema-first validation for Go: schemas are values you build, compose and share, and parsing returns a typed result or a structured error describing every failure.
zgin module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL