OPEN SWAG GO
API Reference

Schema Package

pkg/schema converts Go structs into OpenAPI 3.x JSON Schema definitions. It reads struct tags to infer field names, descriptions, examples, formats, and validation rules.

import "github.com/gopackx/open-swag-go/pkg/schema"

Schema

The generated JSON Schema representation of a Go type.

type Schema struct {
    Type                 string              // OpenAPI type: "string", "integer", "number", "boolean", "array", "object"
    Format               string              // Format hint: "int64", "float", "date-time", "email", etc.
    Properties           map[string]*Schema  // Nested object properties
    Items                *Schema             // Array element schema
    AdditionalProperties *Schema             // Schema for map values (v1.1.0+)
    Required             []string            // Required property names
    Description          string              // Field description
    Example              interface{}         // Example value
    Enum                 []interface{}       // Allowed values
    Nullable             bool                // Whether null is accepted (set for pointer fields)
    ReadOnly             bool                // Mark field as read-only (v1.1.0+)
    WriteOnly            bool                // Mark field as write-only (v1.1.0+)
    Deprecated           bool                // Mark field as deprecated (v1.1.0+)
    MinItems             *int                // Minimum array length (v1.1.0+)
    MaxItems             *int                // Maximum array length (v1.1.0+)
    UniqueItems          bool                // Array items must be unique (v1.1.0+)
    Ref                  string              // $ref pointer; set for named structs when converting with a Registry (v1.3.0+)
    AllOf                []*Schema           // Composition: must match all (v1.1.0+)
    OneOf                []*Schema           // Composition: must match exactly one (v1.1.0+)
    AnyOf                []*Schema           // Composition: must match at least one (v1.1.0+)
}

FromType

func FromType(v interface{}) *Schema

Accepts any Go value (typically a struct) and returns its OpenAPI schema. Nested structs, slices, maps, and pointers are resolved recursively, and every struct is inlined: FromType never sets Ref. A struct that refers back to itself is cut off at the back-edge with {Type: "object", Description: "Recursive reference to ..."}. Use FromTypeWithRegistry to get $ref components instead. Embedded (anonymous) struct fields are promoted into the parent schema (v1.1.0+).

Pointers nested inside the type (struct fields, slice elements, map values) produce the element schema with Nullable: true. A top-level pointer is not nullable: FromType(&User{}) returns the same schema as FromType(User{}), because the pointer only reflects how the value was passed. This is also why RequestBody.Schema: &CreateUserRequest{} does not make the body nullable.

func FromReflectType(t reflect.Type) *Schema

Like FromType, but takes a reflect.Type; here a pointer type does yield a nullable schema. It also inlines every struct.

When the spec is built, Nullable is written OpenAPI 3.1 style as a type array ("type": ["string", "null"]) and the 3.0 nullable keyword is never emitted (v1.3.0+). Note that marshalling a schema.Schema directly with encoding/json (as in the Quick Example below) writes the struct's own nullable field; only the spec output (docs.SpecJSON()) uses the type array.

type User struct {
    ID    int64  `json:"id"    description:"Unique identifier" example:"42"`
    Name  string `json:"name"  description:"Display name"      example:"Alice"`
    Email string `json:"email" description:"Email address"     format:"email"`
}
 
s := schema.FromType(User{})
// s.Type == "object"
// s.Properties["id"].Type == "integer"
// s.Properties["id"].Format == "int64"

Embedded Structs (v1.1.0+)

Embedded (anonymous) struct fields have their properties promoted into the parent schema, matching Go's field promotion semantics:

type Timestamps struct {
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}
 
type User struct {
    Timestamps           // Embedded — fields are promoted
    ID   int64  `json:"id"`
    Name string `json:"name"`
}
 
s := schema.FromType(User{})
// s.Properties has: "id", "name", "created_at", "updated_at"
// Timestamps fields are promoted into User's schema

Since v1.3.0 the rules follow encoding/json:

  • An embedded struct with an unexported type (type base struct{...} embedded as base) is promoted too. Earlier versions dropped its fields.
  • A field declared directly on the outer struct shadows a promoted field with the same JSON name, regardless of field order.
  • A json name on the embedded field (base `json:"base"` ) makes it an ordinary nested property instead of flattening it.

Schema Components (v1.3.0+)

docs.BuildSpec() converts request and response bodies with a Registry, which is how named structs end up under components.schemas. The same API is exported for building schemas outside openswag.Docs. See Schema Components for the naming rules and the generated JSON.

const RefPrefix = "#/components/schemas/"
 
func FromTypeWithRegistry(v interface{}, reg *Registry) *Schema
func FromReflectTypeWithRegistry(t reflect.Type, reg *Registry) *Schema

FromTypeWithRegistry is FromType with named schema components: every named struct type reached from v, including v's own type, is converted once, stored in reg, and returned as &Schema{Ref: RefPrefix + name}. Anonymous structs (struct{ ... }) stay inline, and embedded structs are still flattened into their parent (their own named-struct fields do become $refs). A self-referencing struct produces a real $ref back to itself, because the name is reserved before the fields are walked. time.Time is a string, not a component. A nil registry behaves exactly like FromType / FromReflectType.

On a struct-typed field, only Description and Nullable are kept next to the Ref; the other keywords that field tags would add (example, enum, format, validator bounds) are dropped.

Registry

type Registry struct {
    // unexported fields
}
 
func NewRegistry() *Registry
 
func (r *Registry) Names() []string                       // component names in registration order
func (r *Registry) Schema(name string) *Schema            // the component registered under name, or nil
func (r *Registry) Len() int                              // number of registered components
func (r *Registry) NameOf(t reflect.Type) (string, bool)  // the name assigned to t, if registered

A component is named after its Go type, sanitised to the component key alphabet [A-Za-z0-9_.-]: package paths inside generic type arguments are reduced to the package name and other characters become _, so Page[github.com/acme/dto.User] is named Page_dto.User. When a different type with an already-taken name is registered, it gets its package name as a prefix (model.User → modelUser), then a numeric suffix if that is taken too (modelUser2). The first type registered keeps the bare name, so names are deterministic for a given registration order. All methods are safe on a nil *Registry. A Registry is not safe for concurrent use.

reg := schema.NewRegistry()
body := schema.FromTypeWithRegistry(CreateOrderRequest{}, reg)
// body.Ref == "#/components/schemas/CreateOrderRequest"
 
for _, name := range reg.Names() {
    s := reg.Schema(name) // one entry per named struct reached from CreateOrderRequest
    _ = s
}

The returned schemas are schema.Schema values: marshalling them directly writes "nullable": true on a nullable $ref. Only the spec output (docs.SpecJSON()) rewrites it to {"oneOf": [{"$ref": ...}, {"type": "null"}]}.

Type Mappings

The schema generator maps Go types to OpenAPI types automatically.

Go TypeOpenAPI TypeOpenAPI Format
stringstring—
boolboolean—
intinteger—
int32integerint32
int64integerint64
float32numberfloat
float64numberdouble
[]Tarray— (items = schema of T)
[]bytestringbyte (v1.1.0+)
map[string]Tobject— (additionalProperties = schema of T)
named structobject, or $ref when converted with a Registry (v1.3.0+)—
*T[T, "null"] in the spec (v1.3.0+)format of T (Nullable: true; top-level pointers are not nullable)
time.Timestringdate-time
time.Durationstringduration (v1.1.0+)
uuid.UUIDstringuuid
interface{}/any— (empty schema)— accepts any type (v1.1.0+)

Tag Parsing

The schema generator reads the following struct tags to enrich the generated schema.

TagPurposeExample
jsonField name and omitemptyjson:"email,omitempty"
descriptionField descriptiondescription:"User email"
exampleExample valueexample:"alice@example.com"
formatOpenAPI format overrideformat:"email"
swaggerrequired, format=..., description=..., example=... onlyswagger:"required,format=email"
validatego-playground/validator rules, mapped to constraints (v1.3.0+)validate:"required,email,max=255"
bindingGin-style binding rules, same mapping as validatebinding:"required,min=8"
enumAllowed values (comma-separated), typed; wins over oneof (v1.3.0+)enum:"active,inactive,banned"

The swagger tag does not support enum=, nullable, readOnly, writeOnly or deprecated; those values are ignored. Use a pointer type for nullable fields and the enum tag or validate:"oneof=..." for enums.

Validator rules (validate and binding) map as follows: oneof=a b c → enum (values converted to the field type; single-quoted values may contain spaces); min/gte and max/lte → minLength/maxLength for strings, minimum/maximum for numbers, minItems/maxItems for slices; gt/lt → exact N+1/N-1 for lengths and counts, approximated as the inclusive minimum/maximum for numbers (no exclusiveMinimum/exclusiveMaximum); len=N → both bounds; email, uuid/uuid3/uuid4/uuid5, url/uri (→ uri), ipv4, ipv6, hostname → format, only if no format is set yet; dive,<rules> applies the following rules to the slice items. Rules containing |, parameters that do not parse as numbers, and unmapped rules such as unique are ignored. If the field is nullable, null is appended to the enum.

ParseFieldTags / IsRequired

func ParseFieldTags(field reflect.StructField, schema *Schema)
func IsRequired(field reflect.StructField) bool

ParseFieldTags reads the recognised tags from a struct field and applies them onto the given *Schema: example (type-coerced), description, format, the validator rules from validate and binding, enum, and finally the swagger sub-keys. IsRequired reports whether a field is required: it matches only a bare required rule in the swagger, validate or binding tag. Conditional rules such as required_if, required_with or required_without do not count (v1.3.0+). IsRequired does not look at omitempty; the struct converter applies that rule (see below).

Since v1.3.0 the same two functions are used for Endpoint.QueryParams / Endpoint.PathParams struct fields, so body and parameter tags behave the same way.

Omitempty and Required Interaction (v1.1.0+)

Fields with json:",omitempty" are never marked as required, even if validate:"required" is present. The omitempty tag takes precedence because it signals that the field is optional at the JSON level.

type Product struct {
    Name   string `json:"name"                validate:"required"` // required ✓
    Status string `json:"status,omitempty"    validate:"required"` // NOT required (omitempty wins)
}

Usage

type Product struct {
    ID     int64   `json:"id"     description:"Product ID"   example:"1"`
    Name   string  `json:"name"   description:"Product name" example:"Widget" validate:"required"`
    Price  float64 `json:"price"  description:"Unit price"   example:"9.99"   format:"double"`
    Status string  `json:"status" description:"Availability" enum:"active,discontinued"`
}
 
s := schema.FromType(Product{})
// s.Properties["status"].Enum == ["active", "discontinued"]
// s.Properties["price"].Format == "double"
// s.Required == ["name"]

Quick Example

package main
 
import (
    "encoding/json"
    "fmt"
 
    "github.com/gopackx/open-swag-go/pkg/schema"
)
 
type CreateOrderRequest struct {
    ProductID int64  `json:"product_id" description:"Product to order" example:"42" validate:"required"`
    Quantity  int    `json:"quantity"   description:"Number of items"  example:"2"  validate:"required"`
    Note      string `json:"note"       description:"Optional note"    example:"Gift wrap please"`
}
 
func main() {
    s := schema.FromType(CreateOrderRequest{})
    data, _ := json.MarshalIndent(s, "", "  ")
    fmt.Println(string(data))
}