
pedantigo: Pydantic-inspired Go validation and LLM JSON Schema
pedantigo is an MIT-licensed Go library that unmarshals JSON into structs while it enforces constraints declared in struct tags. It also generates JSON Schema from the same structs. The design follows Pydantic, the Python validation library.
This post covers version 1.1.0.
The problem
Go services read JSON from outside sources: API clients, AI agents and user input. Plain json.Unmarshal checks syntax and types. It does not check that an email is an email or that an age is at least 18. You then write that logic by hand for each struct.
pedantigo keeps the rules next to the fields, and one call applies them.
Install
go get github.com/SmrutAI/pedantigoThe module needs Go 1.21 or later. The import path is case sensitive, so write SmrutAI with capital letters.
Check input and read the errors
A constraint is a rule in a struct tag, such as min=18. The library has more than 100 built-in constraints. This sample uses three of them.
package main
import (
"errors"
"fmt"
"github.com/SmrutAI/pedantigo"
)
type User struct {
Email string `json:"email" pedantigo:"required,email"`
Age int `json:"age" pedantigo:"min=18"`
}
func main() {
data := []byte(`{"email":"notanemail","age":15}`)
user, err := pedantigo.Unmarshal[User](data)
var ve *pedantigo.ValidationError
if errors.As(err, &ve) {
for _, fe := range ve.Errors {
fmt.Printf("%s [%s]: %s\n",
fe.Field, fe.Code, fe.Message)
}
}
fmt.Println(user != nil) // the struct still comes back
}Unmarshal returns a *pedantigo.ValidationError that lists every failing field. For this input the messages are must be a valid email address (code INVALID_EMAIL) and must be at least 18 (code MIN_VALUE). The struct also comes back, so you can inspect what arrived.
Invalid JSON syntax is a different failure. It returns a plain error, not a ValidationError.
Two details trip up new users:
- A value-constraint error names the Go struct field, such as
Email. A missing required key reports the JSON key. - The library ignores a constraint name it does not know, without any error. Stick to documented names:
minandmax(length for strings, slices and maps; value for numbers),gt,gte,lt,lte,regexpandoneof.
Generate a schema for a tool call
LLM tool calling needs a JSON Schema for each tool's arguments. pedantigo builds one from the struct you already check input with.
package main
import (
"fmt"
"github.com/SmrutAI/pedantigo"
)
type User struct {
Name string `json:"name" pedantigo:"required,min=3"`
Email string `json:"email" pedantigo:"required,email"`
Age int `json:"age" pedantigo:"gte=18,lte=120"`
}
func main() {
b, err := pedantigo.SchemaJSON[User]()
if err != nil {
panic(err)
}
fmt.Println(string(b))
}The schema has type: object. The required list holds the required fields. min=3 on the name becomes minLength: 3, and email becomes format: email. On numbers, gte and lte map to minimum and maximum, while gt and lt map to exclusiveMinimum and exclusiveMaximum. SchemaJSON inlines nested structs instead of using $ref. SchemaJSONOpenAPI uses $ref into $defs.
Check a streamed answer
LLM APIs stream a JSON answer in chunks. NewStreamParser collects the chunks and validates the result once the JSON is complete.
package main
import (
"fmt"
"github.com/SmrutAI/pedantigo"
)
type StreamUser struct {
Name string `json:"name" pedantigo:"required"`
Email string `json:"email" pedantigo:"email"`
Age int `json:"age" pedantigo:"min=0"`
}
func main() {
p := pedantigo.NewStreamParser[StreamUser]()
chunks := []string{
`{"name":"Jo`,
`hn","email":"not-an-email","age":25}`,
}
for _, c := range chunks {
obj, st, err := p.Feed([]byte(c))
if !st.IsComplete {
fmt.Println("waiting,", st.BytesReceived, "bytes")
continue
}
fmt.Println(obj.Name, err) // obj is set even when err is not
p.Reset()
}
}The parser does not repair broken JSON. Feed returns a nil struct and no error until the buffer holds valid JSON. A complete payload that breaks a rule returns the struct and an error, and the error text names the field.
What to read next
- Quickstart covers installation and first steps.
- Schema generation explains the schema functions.
- Streaming validation covers the stream parser.
- Constraints lists the built-in rules.
- The source is on GitHub.
Related posts
pedantigo v2: check request bodies in Echo and Gin
pedantigo v2 adds a plugin system, a new default tag name, and binders for Echo and Gin. Learn what changed, how to migrate, and which versions to avoid.

multipay-india: one Go API for Cashfree and Razorpay
multipay-india is an open-source Go library that puts Indian payment gateways behind one API for orders, subscriptions, plan changes and webhooks.

Caching, Part 3: how Smritea uses smartcache
How Smritea uses its open-source smartcache library: write-then-evict, tenant-safe read-through, fail-open versus fail-closed choices, and per-app prefix eviction.




