Skip to main content

JSON Schema

Types and utilities for working with JSON Schema in structured output generation.

Schema Interface​

type Schema interface {
Validator() Validator
}

Interface for schema definitions that can be used with structured output.

Validator Interface​

type Validator interface {
Validate(data interface{}) error
JSONSchema() map[string]interface{}
}

Interface for validating data against a schema.

Creating Schemas​

JSONSchemaValidator​

func NewJSONSchema(schema map[string]interface{}) *JSONSchemaValidator

Creates a validator from a JSON Schema object.

SimpleJSONSchema​

func NewSimpleJSONSchema(schema map[string]interface{}) *SimpleJSONSchema

Creates a simple JSON schema implementation.

StructValidator​

func NewStructSchema(targetType reflect.Type) *StructValidator

Creates a validator from a Go struct type.

Examples​

Basic JSON Schema​

package main

import (
"context"
"fmt"
"log"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
"github.com/digitallysavvy/go-ai/pkg/schema"
)

func main() {
personSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{
"type": "string",
"description": "Person's full name",
},
"age": map[string]interface{}{
"type": "integer",
"minimum": 0,
"description": "Person's age in years",
},
"email": map[string]interface{}{
"type": "string",
"format": "email",
"description": "Email address",
},
},
"required": []string{"name", "age"},
"additionalProperties": false,
})

p := openai.New(openai.Config{APIKey: "your-api-key"})
model, err := p.LanguageModel("gpt-4")
if err != nil {
log.Fatal(err)
}

// Use with GenerateObject
result, err := ai.GenerateObject(context.Background(), ai.GenerateObjectOptions{
Model: model,
Prompt: "Generate a person",
Schema: personSchema,
})
if err != nil {
log.Fatal(err)
}

fmt.Printf("%+v\n", result.Object)
}

Array Schema​

tasksSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{"type": "integer"},
"description": map[string]interface{}{"type": "string"},
"completed": map[string]interface{}{"type": "boolean"},
},
"required": []string{"id", "description", "completed"},
},
})

Nested Schema​

companySchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{"type": "string"},
"address": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"street": map[string]interface{}{"type": "string"},
"city": map[string]interface{}{"type": "string"},
"country": map[string]interface{}{"type": "string"},
},
"required": []string{"city", "country"},
},
"employees": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{"type": "string"},
"position": map[string]interface{}{"type": "string"},
},
},
},
},
"required": []string{"name"},
})

Enum Schema​

statusSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "string",
"enum": []string{"pending", "in_progress", "completed", "cancelled"},
})

With Patterns and Constraints​

userSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"username": map[string]interface{}{
"type": "string",
"pattern": "^[a-zA-Z0-9_]{3,20}$",
"minLength": 3,
"maxLength": 20,
},
"password": map[string]interface{}{
"type": "string",
"minLength": 8,
},
"age": map[string]interface{}{
"type": "integer",
"minimum": 13,
"maximum": 120,
},
},
"required": []string{"username", "password"},
})

Using with GenerateObject​

recipeSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{
"type": "string",
"description": "Name of the recipe",
},
"ingredients": map[string]interface{}{
"type": "array",
"description": "List of ingredients",
"items": map[string]interface{}{"type": "string"},
},
"steps": map[string]interface{}{
"type": "array",
"description": "Cooking steps",
"items": map[string]interface{}{"type": "string"},
},
"cookTime": map[string]interface{}{
"type": "integer",
"description": "Cooking time in minutes",
},
},
"required": []string{"name", "ingredients", "steps"},
})

result, err := ai.GenerateObject(ctx, ai.GenerateObjectOptions{
Model: model,
Prompt: "Generate a recipe for chocolate chip cookies",
Schema: recipeSchema,
})
if err != nil {
log.Fatal(err)
}

Validating Data​

schema := schema.NewSimpleJSONSchema(personSchema)

data := map[string]interface{}{
"name": "John Doe",
"age": 30,
}

if err := schema.Validator().Validate(data); err != nil {
log.Printf("Validation failed: %v", err)
}

Getting JSON Schema​

schema := schema.NewSimpleJSONSchema(personSchema)
jsonSchema := schema.Validator().JSONSchema()

// Returns the original schema map
fmt.Printf("Schema: %+v\n", jsonSchema)

JSON Schema Types​

Common JSON Schema property types:

  • "string" - String values
  • "integer" - Integer numbers
  • "number" - Any numbers (including floats)
  • "boolean" - Boolean values
  • "object" - Nested objects
  • "array" - Arrays of items
  • "null" - Null values

Common Constraints​

String Constraints​

"minLength": 1,
"maxLength": 100,
"pattern": "^[A-Z][a-z]+$",
"format": "email", // or "uri", "date-time", etc.

Number Constraints​

"minimum": 0,
"maximum": 100,
"multipleOf": 5,

Array Constraints​

"minItems": 1,
"maxItems": 10,
"uniqueItems": true,

Object Constraints​

"required": []string{"field1", "field2"},
"additionalProperties": false,
"minProperties": 1,
"maxProperties": 10,

Applying Schema Defaults​

schema.ApplyDefaults returns a copy of a value with JSON Schema object defaults applied, walking object properties and array items recursively. The SDK runs this before validation wherever it validates externally-supplied input against a JSON Schema — MCP structured tool output, ValidateUIMessages, tool-approval revalidation, agent call options, harness tool ContextSchema, and workflow.ValidateSerializableToolInput — so a missing field that has a default in the schema no longer fails validation.

func ApplyDefaults(value interface{}, schema Schema) interface{}
withDefaults := schema.ApplyDefaults(input, mySchema)
if err := mySchema.Validator().Validate(withDefaults); err != nil {
return err
}

Circular $ref Detection​

A $ref cycle that never consumes input — for example two schemas that reference each other only through allOf/anyOf/oneOf/not with no concrete type in between — now returns a validation error instead of overflowing the stack. This applies to both Validate and ApplyDefaults. A not/anyOf/oneOf branch that simply fails to match its subschema is unaffected; only a genuine reference cycle is rejected.

See Also​