GORM provides comprehensive schema parsing and metadata capabilities through the gorm.io/gorm/schema package. This allows runtime inspection of model structures, field types, relationships, and naming conventions.
import "gorm.io/gorm/schema"Parse a model into a schema structure.
func Parse(dest interface{}, cacheStore *sync.Map, namer Namer) (*Schema, error)
func ParseWithSpecialTableName(dest interface{}, cacheStore *sync.Map, namer Namer, specialTableName string) (*Schema, error)Usage:
import (
"sync"
"gorm.io/gorm/schema"
)
type User struct {
ID uint
Name string
Email string
CompanyID uint
Company Company
}
// Parse schema
s, err := schema.Parse(&User{}, &sync.Map{}, schema.NamingStrategy{})
if err != nil {
// Handle error
}
fmt.Println("Table name:", s.Table)
fmt.Println("Primary fields:", len(s.PrimaryFields))
// Access through DB
stmt := &gorm.Statement{DB: db}
if err := stmt.Parse(&User{}); err != nil {
// Handle error
}
schema := stmt.SchemaThe Schema struct contains all metadata about a model.
type Schema struct {
Name string
ModelType reflect.Type
Table string
PrioritizedPrimaryField *Field
DBNames []string
PrimaryFields []*Field
PrimaryFieldDBNames []string
Fields []*Field
FieldsByName map[string]*Field
FieldsByBindName map[string]*Field
FieldsByDBName map[string]*Field
FieldsWithDefaultDBValue []*Field
Relationships Relationships
CreateClauses []clause.Interface
QueryClauses []clause.Interface
UpdateClauses []clause.Interface
DeleteClauses []clause.Interface
BeforeCreate bool
AfterCreate bool
BeforeUpdate bool
AfterUpdate bool
BeforeDelete bool
AfterDelete bool
BeforeSave bool
AfterSave bool
AfterFind bool
}// Find field by name (searches by name, db name, and bind name)
func (schema *Schema) LookUpField(name string) *Field
// Find field by bind names
func (schema *Schema) LookUpFieldByBindName(bindNames []string, name string) *FieldUsage:
// Look up field
field := schema.LookUpField("Name")
if field != nil {
fmt.Println("Field DB name:", field.DBName)
fmt.Println("Field type:", field.DataType)
}
// Look up by database name
field = schema.LookUpField("user_name")The Field struct contains metadata about a model field.
type Field struct {
Name string
DBName string
BindNames []string
EmbeddedBindNames []string
DataType DataType
GORMDataType DataType
PrimaryKey bool
AutoIncrement bool
AutoIncrementIncrement int64
Creatable bool
Updatable bool
Readable bool
AutoCreateTime TimeType
AutoUpdateTime TimeType
HasDefaultValue bool
DefaultValue string
DefaultValueInterface interface{}
NotNull bool
Unique bool
Comment string
Size int
Precision int
Scale int
IgnoreMigration bool
FieldType reflect.Type
IndirectFieldType reflect.Type
StructField reflect.StructField
Tag reflect.StructTag
TagSettings map[string]string
Schema *Schema
EmbeddedSchema *Schema
OwnerSchema *Schema
ReflectValueOf func(context.Context, reflect.Value) reflect.Value
ValueOf func(context.Context, reflect.Value) (interface{}, error)
Set func(context.Context, reflect.Value, interface{}) error
Serializer SerializerInterface
NewValuePool FieldNewValuePool
UniqueIndex string
}type DataType string
const (
Bool DataType = "bool"
Int DataType = "int"
Uint DataType = "uint"
Float DataType = "float"
String DataType = "string"
Time DataType = "time"
Bytes DataType = "bytes"
)type TimeType int64
const (
UnixTime TimeType = 1 // Unix timestamp
UnixSecond TimeType = 2 // Unix seconds
UnixMillisecond TimeType = 3 // Unix milliseconds
UnixNanosecond TimeType = 4 // Unix nanoseconds
)type Relationships struct {
HasOne []*Relationship
BelongsTo []*Relationship
HasMany []*Relationship
Many2Many []*Relationship
Relations map[string]*Relationship
EmbeddedRelations map[string]*Relationships
}type RelationshipType string
const (
HasOne RelationshipType = "has_one"
HasMany RelationshipType = "has_many"
BelongsTo RelationshipType = "belongs_to"
Many2Many RelationshipType = "many_to_many"
)
type Relationship struct {
Name string
Type RelationshipType
Field *Field
Polymorphic *Polymorphic
References []*Reference
Schema *Schema
FieldSchema *Schema
JoinTable *Schema
}type Polymorphic struct {
PolymorphicID *Field
PolymorphicType *Field
Value string
}type Reference struct {
PrimaryKey *Field
PrimaryValue string
ForeignKey *Field
OwnPrimaryKey bool
}type Namer interface {
TableName(table string) string
SchemaName(table string) string
ColumnName(table, column string) string
JoinTableName(joinTable string) string
RelationshipFKName(Relationship) string
CheckerName(table, column string) string
IndexName(table, column string) string
UniqueName(table, column string) string
}The default naming strategy implementation.
type NamingStrategy struct {
TablePrefix string
SingularTable bool
NameReplacer Replacer
NoLowerCase bool
IdentifierMaxLength int
}Usage:
import "gorm.io/gorm/schema"
// Custom naming strategy
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_", // Table prefix
SingularTable: true, // Use singular table names
NoLowerCase: true, // Don't lowercase names
},
})
// Usage examples:
// User struct -> t_User table (with config above)
// User struct -> t_users table (default)type Replacer interface {
Replace(name string) string
}Implement this interface to customize table name.
type Tabler interface {
TableName() string
}Usage:
type User struct {
ID uint
Name string
}
func (User) TableName() string {
return "custom_users"
}
// User model will use "custom_users" tabletype TablerWithNamer interface {
TableName(Namer) string
}Usage:
type User struct {
ID uint
Name string
}
func (User) TableName(namer schema.Namer) string {
// Use namer to transform name
return namer.TableName("users")
}type Index struct {
Name string
Class string
Type string
Where string
Comment string
Option string
Fields []IndexOption
Composite bool
}type IndexOption struct {
DBName string
Expression string
Sort string
Collate string
Length int
}Usage:
type User struct {
ID uint
Name string `gorm:"index:idx_name,unique,sort:desc,length:100"`
Age int `gorm:"index:idx_age_active,composite:age_active"`
Active bool `gorm:"index:idx_age_active,composite:age_active"`
}type Constraint struct {
Name string
Field *Field
Schema *Schema
ForeignKeys []*Field
ReferenceSchema *Schema
References []*Field
OnDelete string
OnUpdate string
}type CheckConstraint struct {
Name string
Constraint string
Field *Field
}type UniqueConstraint struct {
Name string
Field *Field
}type ConstraintInterface interface {
GetName() string
Build() (sql string, vars []interface{})
}Serializers control how field values are stored in and retrieved from the database.
type SerializerInterface interface {
SerializerValuerInterface
Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) error
}
type SerializerValuerInterface interface {
Value(ctx context.Context, field *Field, dst reflect.Value, fieldValue interface{}) (interface{}, error)
}GORM provides several built-in serializers:
JSONSerializer - Serializes data as JSONUnixSecondSerializer - Stores time as Unix timestampGobSerializer - Uses Go's gob encodingfunc RegisterSerializer(name string, serializer SerializerInterface)
func GetSerializer(name string) (SerializerInterface, bool)Usage:
import "gorm.io/gorm/schema"
// Register custom serializer
schema.RegisterSerializer("custom", &CustomSerializer{})
// Use in model
type User struct {
ID uint
Metadata map[string]interface{} `gorm:"serializer:json"`
Config Settings `gorm:"serializer:custom"`
}
// Implement custom serializer
type CustomSerializer struct{}
func (CustomSerializer) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) error {
// Deserialize from database value
return nil
}
func (CustomSerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
// Serialize to database value
return nil, nil
}Implement this interface to provide custom database data type.
type GormDataTypeInterface interface {
GormDataType() string
}Usage:
import (
"database/sql/driver"
"encoding/json"
)
type JSON json.RawMessage
func (JSON) GormDataType() string {
return "json"
}
func (j JSON) Value() (driver.Value, error) {
if len(j) == 0 {
return nil, nil
}
return string(j), nil
}
func (j *JSON) Scan(value interface{}) error {
if value == nil {
*j = JSON("null")
return nil
}
s, ok := value.([]byte)
if !ok {
return errors.New("invalid Scan source")
}
*j = JSON(s)
return nil
}
// Use in model
type User struct {
ID uint
Metadata JSON
}Models can implement these interfaces to provide custom clauses.
type CreateClausesInterface interface {
CreateClauses(*Field) []clause.Interface
}
type QueryClausesInterface interface {
QueryClauses(*Field) []clause.Interface
}
type UpdateClausesInterface interface {
UpdateClauses(*Field) []clause.Interface
}
type DeleteClausesInterface interface {
DeleteClauses(*Field) []clause.Interface
}type FieldNewValuePool interface {
Get() interface{}
Put(interface{})
}This interface allows implementing custom value pooling for fields to reduce allocations.
const DefaultAutoIncrementIncrement int64 = 1import "gorm.io/gorm/schema"
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:256;not null"`
Email string `gorm:"uniqueIndex"`
Age int
CompanyID uint
Company Company
}
// Parse schema
s, _ := schema.Parse(&User{}, &sync.Map{}, schema.NamingStrategy{})
// Inspect fields
for _, field := range s.Fields {
fmt.Printf("Field: %s, DB: %s, Type: %s\n",
field.Name, field.DBName, field.DataType)
if field.PrimaryKey {
fmt.Println(" - Primary Key")
}
if field.Unique {
fmt.Println(" - Unique")
}
if field.NotNull {
fmt.Println(" - Not Null")
}
if field.Size > 0 {
fmt.Printf(" - Size: %d\n", field.Size)
}
}
// Inspect relationships
for name, rel := range s.Relationships.Relations {
fmt.Printf("Relationship: %s, Type: %s\n", name, rel.Type)
}// Get table name at runtime
stmt := &gorm.Statement{DB: db}
stmt.Parse(&User{})
tableName := stmt.Schema.Table
// With custom naming strategy
namer := schema.NamingStrategy{TablePrefix: "app_"}
customTable := namer.TableName("users") // "app_users"// Access field tags
field := schema.LookUpField("Email")
if field != nil {
// TagSettings is a parsed map of tag settings
if size, ok := field.TagSettings["SIZE"]; ok {
fmt.Println("Field size:", size)
}
// Check for specific tags
if _, ok := field.TagSettings["UNIQUE"]; ok {
fmt.Println("Field is unique")
}
}// Use a cache for parsed schemas
cache := &sync.Map{}
// First parse - parses and caches
s1, _ := schema.Parse(&User{}, cache, schema.NamingStrategy{})
// Second parse - retrieves from cache
s2, _ := schema.Parse(&User{}, cache, schema.NamingStrategy{})
// s1 and s2 point to the same schema object// Fields provide value access functions
field := schema.LookUpField("Name")
// Get field value from model
userValue := reflect.ValueOf(&user)
fieldValue := field.ReflectValueOf(context.Background(), userValue)
// Get field value as interface
value, err := field.ValueOf(context.Background(), userValue)
// Set field value
err = field.Set(context.Background(), userValue, "Alice")GORM provides built-in serializers for custom field serialization. Serializers allow you to store complex data types in database columns.
// SerializerInterface defines field serializer
type SerializerInterface interface {
Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) error
SerializerValuerInterface
}
// SerializerValuerInterface defines value serialization
type SerializerValuerInterface interface {
Value(ctx context.Context, field *Field, dst reflect.Value, fieldValue interface{}) (interface{}, error)
}GORM includes three built-in serializers:
// JSONSerializer serializes values as JSON
type JSONSerializer struct{}
// UnixSecondSerializer serializes time.Time as Unix seconds
type UnixSecondSerializer struct{}
// GobSerializer serializes values using Go's gob encoding
type GobSerializer struct{}// Register a custom serializer
func RegisterSerializer(name string, serializer SerializerInterface)
// Get a registered serializer
func GetSerializer(name string) (serializer SerializerInterface, ok bool)Usage:
import "gorm.io/gorm/schema"
// Use JSON serializer on a field
type User struct {
ID uint
Name string
Metadata map[string]interface{} `gorm:"serializer:json"`
}
// Use UnixTime serializer
type Event struct {
ID uint
Timestamp int64 `gorm:"serializer:unixtime"`
}
// Use Gob serializer
type Config struct {
ID uint
Settings interface{} `gorm:"serializer:gob"`
}
// Register custom serializer
schema.RegisterSerializer("custom", MyCustomSerializer{})
// Use custom serializer
type Record struct {
ID uint
Data string `gorm:"serializer:custom"`
}