Pydantic for Go - JSON schema generation, LLM response validation, partial JSON streaming. Catch hallucinated types before they crash your app. Works with OpenAI, Anthropic, Gemini, Gin REST APIs (with OpenAPI generation), and any system that consumes JSON Schema. Inspired by Python's Pydantic and FastAPI.
func (u *User) FieldEmail() godantic.FieldOptions[string] {
return godantic.Field(
godantic.Required[string](),
godantic.Email(),
godantic.Description[string]("Primary contact email"),
)
}Key Features:
- LLM-ready schemas: Generate flattened JSON Schema for OpenAI, Anthropic, Gemini structured outputs
- Streaming partial JSON: Parse incomplete JSON as it streams from LLM APIs for real-time UI updates
- Runtime validation: Catch hallucinated types, missing fields, and invalid enums before they crash your app
- Union type support: Full
anyOf/oneOfschemas with discriminators - No struct tags: Uses
Field{Name}()methods—works with your IDE, tests, and debugger - Gin integration: Automatic OpenAPI generation and interactive docs
go get github.com/deepankarm/godanticGodantic solves a common problem: LLMs sometimes return invalid data (wrong types, missing fields, out-of-range values). Define your schema once, use it to guide LLM responses AND validate them.
import (
"github.com/deepankarm/godantic"
"github.com/deepankarm/godantic/schema"
)
// 1. Define your response structure
type TaskList struct {
Tasks []Task
}
type Task struct {
Title string
Priority string
Done bool
}
func (t *TaskList) FieldTasks() godantic.FieldOptions[[]Task] {
return godantic.Field(
godantic.Required[[]Task](),
godantic.MinItems[Task](1),
)
}
func (t *Task) FieldPriority() godantic.FieldOptions[string] {
return godantic.Field(
godantic.OneOf("low", "medium", "high"),
)
}
// 2. Generate flattened schema for LLM APIs
schemaGen := schema.NewGenerator[TaskList]()
flatSchema, _ := schemaGen.GenerateFlattened()
// 3. Send schema to your LLM provider (OpenAI, Gemini, Claude)
jsonData := callLLM(prompt, flatSchema)
// 4. Validate response before using it
validator := godantic.NewValidator[TaskList]()
result, errs := validator.Unmarshal(jsonData)
if len(errs) > 0 {
// LLM returned invalid data - handle or retry
}Parse incomplete JSON as it streams from LLM APIs. Essential for real-time UI updates during long-running generation.
// Create streaming parser
parser := godantic.NewStreamParser[Response]()
// Feed chunks as they arrive from LLM API
for chunk := range llmStream {
result, state, _ := parser.Feed(chunk)
if state.IsComplete {
// Full response received and validated
fmt.Println("Complete:", result)
} else {
// Show partial data in real-time
// state.WaitingFor() tells you which fields are still incomplete
fmt.Printf("Streaming... waiting for: %v\n", state.WaitingFor())
}
}How it works:
- Repairs incomplete JSON (closes unclosed strings, arrays, objects)
- Tracks which fields are still being streamed via
state.WaitingFor() - Skips validation for incomplete fields
- Applies defaults automatically
See examples/llm-partialjson-streaming/ for a complete working example with Gemini streaming.
See complete working examples:
examples/openai-structured-output/- OpenAI structured output APIexamples/gemini-structured-output/- Google Gemini with enums and unions
import "github.com/deepankarm/godantic"
type User struct {
Email string
Age int
Username string
}
// Define validation rules
func (u *User) FieldEmail() godantic.FieldOptions[string] {
return godantic.Field(
godantic.Required[string](),
godantic.Email(),
)
}
func (u *User) FieldAge() godantic.FieldOptions[int] {
return godantic.Field(
godantic.Min(0),
godantic.Max(130),
)
}
func (u *User) FieldUsername() godantic.FieldOptions[string] {
return godantic.Field(
godantic.Required[string](),
godantic.MinLen(3),
godantic.MaxLen(20),
)
}
// Validate
validator := godantic.NewValidator[User]()
errs := validator.Validate(&user)// Numeric constraints
godantic.Field(
godantic.Min(0),
godantic.Max(100),
godantic.MultipleOf(5),
)
// String constraints
godantic.Field(
godantic.MinLen(3),
godantic.MaxLen(20),
godantic.Regex("^[a-zA-Z0-9]+$"),
godantic.Email(),
godantic.URL(),
)
// Enum validation
godantic.Field(
godantic.OneOf("active", "inactive", "pending"),
)func (u *User) FieldPassword() godantic.FieldOptions[string] {
return godantic.Field(
godantic.Required[string](),
godantic.MinLen(8),
godantic.Validate(func(password string) error {
// Your custom logic
if !hasUppercase(password) {
return fmt.Errorf("must contain uppercase")
}
return nil
}),
)
}By design, Go doesn't have native union types. However, when building systems that interact with external APIs, LLMs, or generate OpenAPI schemas, you often need to express "this field can be one of several types" in JSON Schema.
Godantic provides runtime validation and JSON Schema generation for union-like patterns using Go's existing type system (any and interfaces).
1. Simple Unions (anyOf) - For any fields
Runtime validation + schema generation for fields that accept multiple types:
type APIRequest struct {
Query any // Accept string OR array of inputs
}
func (r *APIRequest) FieldQuery() godantic.FieldOptions[any] {
return godantic.Field(
godantic.Union[any]("string", []TextInput{}, []ImageInput{}),
godantic.Description[any]("Text query or structured inputs"),
)
}Generated JSON Schema:
{
"Query": {
"anyOf": [
{"type": "string"},
{"type": "array", "items": {"$ref": "#/$defs/TextInput"}},
{"type": "array", "items": {"$ref": "#/$defs/ImageInput"}}
]
}
}2. Discriminated Unions (oneOf) - For interface fields
Runtime validation + schema generation with discriminator field (OpenAPI 3.1, LLM tool calls):
type ResponseType interface {
GetStatus() string
}
type SuccessResponse struct {
Status string // Must be "success"
Data map[string]string
}
type ErrorResponse struct {
Status string // Must be "error"
Message string
Code int
}
type APIResult struct {
Response ResponseType // Compile-time type safety via interface
}
func (a *APIResult) FieldResponse() godantic.FieldOptions[ResponseType] {
return godantic.Field(
godantic.DiscriminatedUnion[ResponseType]("Status", map[string]any{
"success": SuccessResponse{},
"error": ErrorResponse{},
}),
)
}Generated JSON Schema:
{
"Response": {
"oneOf": [
{"$ref": "#/$defs/SuccessResponse"},
{"$ref": "#/$defs/ErrorResponse"}
],
"discriminator": {
"propertyName": "Status",
"mapping": {
"success": "#/$defs/SuccessResponse",
"error": "#/$defs/ErrorResponse"
}
}
}
}What validation provides:
- Validates
Statusfield is one of["success", "error"] - Catches typos:
Status: "succes"→ validation error - Prevents invalid states:
ErrorResponse{Status: "success"}→ validation error - Works with Go's type system (interfaces for compile-time safety)
- Generates proper JSON Schema for external tools
Real-world benefits:
- LLM integrations: Send proper schemas for structured outputs (OpenAI, Anthropic, Gemini)
- OpenAPI generation: Create API specs that generate type-safe clients
- API validation: Catch data inconsistencies at runtime
- Interoperability: Work with systems that expect union types (TypeScript, Pydantic)
Automatic type routing for interface types based on discriminator fields. Useful for polymorphic API requests, payment methods, or any domain where you need runtime type selection.
type PaymentType string
const (
PaymentTypeCreditCard PaymentType = "credit_card"
PaymentTypePayPal PaymentType = "paypal"
PaymentTypeBankTransfer PaymentType = "bank_transfer"
)
type PaymentMethod interface {
GetType() PaymentType
}
type CreditCardPayment struct {
Type PaymentType `json:"type"`
CardNumber string `json:"card_number"`
}
type PayPalPayment struct {
Type PaymentType `json:"type"`
Email string `json:"email"`
}
type BankTransferPayment struct {
Type PaymentType `json:"type"`
AccountName string `json:"account_name"`
}
// Create validator with discriminator
validator := godantic.NewValidator[PaymentMethod](
godantic.WithDiscriminatorTyped("type", map[PaymentType]any{
PaymentTypeCreditCard: CreditCardPayment{},
PaymentTypePayPal: PayPalPayment{},
PaymentTypeBankTransfer: BankTransferPayment{},
}),
)
// Automatically routes to correct type based on "type" field
payment, errs := validator.Unmarshal(jsonData)
// Type switch for handling
switch p := (*payment).(type) {
case CreditCardPayment:
fmt.Printf("Credit card: %s\n", p.CardNumber)
case PayPalPayment:
fmt.Printf("PayPal: %s\n", p.Email)
case BankTransferPayment:
fmt.Printf("Bank: %s\n", p.AccountName)
}How it works:
- Examines discriminator field (
"type") to determine concrete type - Unmarshals JSON into the appropriate struct (e.g.,
CreditCardPayment) - Validates all fields according to their Field methods
- Applies default values if defined
- Returns as interface type with proper concrete value
Key benefits:
- No manual discriminator routing code required
- Compile-time type safety via Go interfaces
- All field validation rules applied automatically
- Clean separation of validation logic from business logic
See examples/payment-methods/ for a complete working example.
Generate JSON Schema without struct tags:
import "github.com/deepankarm/godantic/schema"
sg := schema.NewGenerator[User]()
s, err := sg.Generate()
// Or as JSON string
jsonSchema, err := sg.GenerateJSON()All validation constraints (min, max, pattern, etc.) are automatically included in the schema.
Godantic provides convenient methods for working with JSON that automatically apply defaults and validate:
Unmarshal - JSON → Struct (with validation)
Unmarshals JSON, applies defaults, and validates in one step:
validator := godantic.NewValidator[User]()
// One-liner: unmarshal + defaults + validate
user, errs := validator.Unmarshal(jsonData)
if len(errs) > 0 {
// Handle validation errors
}
// user is ready to use with all defaults appliedMarshal - Struct → JSON (with validation)
Validates, applies defaults, and marshals to JSON in one step:
validator := godantic.NewValidator[User]()
// One-liner: validate + defaults + marshal
jsonData, errs := validator.Marshal(&user)
if len(errs) > 0 {
// Handle validation errors
}
// jsonData is valid JSON with all defaultsGodantic provides hooks to transform data at different stages of validation and serialization:
BeforeValidate - Transform JSON before validation
Modify the raw JSON map before unmarshaling and validation (useful for backward compatibility):
func (u *User) BeforeValidate(raw map[string]any) error {
// Normalize legacy format
if legacyField, ok := raw["old_field"]; ok {
raw["new_field"] = legacyField
delete(raw, "old_field")
}
return nil
}AfterValidate - Transform struct after validation
Modify the struct after successful validation (useful for computed fields):
func (u *User) AfterValidate() error {
u.DisplayName = strings.ToLower(u.Username)
return nil
}BeforeSerialize - Transform struct before marshaling
Modify the struct before marshaling to JSON:
func (u *User) BeforeSerialize() error {
u.UpdatedAt = time.Now()
return nil
}AfterSerialize - Transform JSON after marshaling
Modify the JSON bytes after marshaling (useful for wrapping responses):
func (u *User) AfterSerialize(data []byte) ([]byte, error) {
// Wrap in envelope
envelope := map[string]any{
"data": json.RawMessage(data),
"version": "1.0",
}
return json.Marshal(envelope)
}Execution order:
- Unmarshal (JSON → Struct):
BeforeValidate→ unmarshal → validate → defaults →AfterValidate - Marshal (Struct → JSON):
BeforeSerialize→ validate → defaults → marshal →AfterSerialize
Works with embedded structs, nested structs, pointers, slices, and maps:
type Company struct {
Name string
Address Address // Nested struct
Contacts []Contact // Slice of structs
Settings map[string]int // Map
}FastAPI experience with Gin. Automatic OpenAPI generation, request validation, and interactive docs—define your types once, get everything else for free.
import "github.com/deepankarm/godantic/pkg/gingodantic"
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
func (c *CreateUserRequest) FieldName() godantic.FieldOptions[string] {
return godantic.Field(godantic.Required[string](), godantic.MinLen(2))
}
func (c *CreateUserRequest) FieldEmail() godantic.FieldOptions[string] {
return godantic.Field(godantic.Required[string](), godantic.Email())
}
// Setup
router := gin.Default()
api := gingodantic.New("User API", "1.0.0")
// Register endpoint with schema
router.POST("/users",
api.OpenAPISchema("POST", "/users",
gingodantic.WithSummary("Create user"),
gingodantic.WithRequest[CreateUserRequest](), // Enables validation
gingodantic.WithResponse[UserResponse](201, "Created"),
),
func(c *gin.Context) {
req, _ := gingodantic.GetValidated[CreateUserRequest](c)
// req is validated and ready to use
},
)
// Serve OpenAPI spec and documentation UIs
router.GET("/openapi.json", api.OpenAPIHandler())
router.GET("/docs", gingodantic.SwaggerUI("/openapi.json"))
router.GET("/redoc", gingodantic.ReDoc("/openapi.json")) // Alternative UIFeatures:
- Automatic validation: Request bodies, query params, path params, headers, and cookies
- OpenAPI 3.0.3 generation: Complete spec with all parameter types and constraints
- Type-safe helpers:
GetValidated[T](),GetValidatedQuery[T](),GetValidatedPath[T](), etc. - Validation by default: Enabled automatically when request types are specified
- Documentation UIs: Built-in Swagger UI and ReDoc handlers
- Zero boilerplate: No manual schema writing or validation middleware
Parameter types supported:
gingodantic.WithRequest[T]() // Request body
gingodantic.WithQueryParams[T]() // Query parameters
gingodantic.WithPathParams[T]() // Path parameters (:id)
gingodantic.WithHeaderParams[T]() // Request headers
gingodantic.WithCookieParams[T]() // Cookies
gingodantic.WithResponse[T](code) // Response schemasAll godantic constraints (min, max, regex, email, etc.) are automatically included in the OpenAPI spec.
See examples/gin-api/ for a complete working API with all parameter types.
godantic.Required[T]() // required field
// numeric constraints
godantic.Min(value) // value >= min
godantic.Max(value) // value <= max
godantic.ExclusiveMin(value) // value > min
godantic.ExclusiveMax(value) // value < max
godantic.MultipleOf(value) // value is multiple of
// string constraints
godantic.MinLen(length) // minimum length
godantic.MaxLen(length) // maximum length
godantic.Regex(pattern) // regex pattern match
godantic.Email() // email format
godantic.URL() // URL format
godantic.ContentEncoding(encoding) // e.g., "base64"
godantic.ContentMediaType(type) // e.g., "application/json"
// array/slice constraints
godantic.MinItems[T](count) // minimum number of items
godantic.MaxItems[T](count) // maximum number of items
godantic.UniqueItems[T]() // all items must be unique
// map/object constraints
godantic.MinProperties(count) // minimum properties
godantic.MaxProperties(count) // maximum properties
// union constraints
godantic.Union[T](type1, type2, ...) // any of the types
godantic.DiscriminatedUnion[T](discriminator, map[string]any{
"type1": type1{},
"type2": type2{},
...
}) // one of the types based on discriminator
// value constraints
godantic.OneOf(value1, value2, ...) // enum - one of allowed values
godantic.Const(value) // must equal exactly this value
godantic.Default(value) // default value (schema only)
// schema metadata
godantic.Description[T](text) // field description
godantic.Example(value) // example value
godantic.Title[T](text) // field title
godantic.Format[T](format) // format hint (e.g., "date-time")
godantic.ReadOnly[T]() // read-only field
godantic.WriteOnly[T]() // write-only field
godantic.Deprecated[T]() // deprecated fieldgodantic.Validate(func(val T) error {
// Your custom validation logic
return nil
})- Define
Field{FieldName}()methods that returnFieldOptions[T] - Use
Field()to compose validation constraints - Create a
Validator[T]and callValidate() - Generate JSON Schema with
schema.NewGenerator[T]()
Zero values (empty string, 0, nil) are treated as "not set" for required field checks.
go test ./... -v -coverCheck out examples/ for complete working examples:
openai-structured-output/- Using godantic with OpenAI's structured output API to extract meeting summariesgemini-structured-output/- Using godantic with Google Gemini to parse task lists with enums and unionsllm-partialjson-streaming/- Streaming partial JSON during long-running generation using Geminipayment-methods/- Validating payment requests interfaces using discriminated unionsgin-api/- Complete Gin REST API with automatic OpenAPI generation and Swagger UI