Configuration
Fory Go uses a functional options pattern for configuration. This allows you to customize serialization behavior while maintaining sensible defaults.
Creating a Fory Instance
Default Configuration
import "github.com/apache/fory/go/fory"
f := fory.New(fory.WithXlang(true))
Default settings:
| Option | Default | Description |
|---|---|---|
| TrackRef | false | Reference tracking disabled |
| MaxDepth | 20 | Maximum nesting depth |
| IsXlang | true | Xlang mode enabled |
| Compatible | true | Compatible schema-evolution metadata enabled |
| MaxTypeFields | 512 | Max fields in one received struct metadata body |
| MaxTypeMetaBytes | 4096 | Max encoded bytes in one received metadata body |
| MaxSchemaVersionsPerType | 10 | Max remote metadata versions for one logical type |
| MaxAverageSchemaVersionsPerType | 3 | Average remote metadata versions across types |
With Options
f := fory.New(
fory.WithXlang(true),
fory.WithTrackRef(true),
fory.WithMaxDepth(10),
fory.WithMaxTypeFields(512),
fory.WithMaxTypeMetaBytes(4096),
fory.WithMaxSchemaVersionsPerType(10),
fory.WithMaxAverageSchemaVersionsPerType(3),
)
Configuration
WithTrackRef
Enable reference tracking to handle circular references and shared objects:
f := fory.New(fory.WithTrackRef(true))
When enabled:
- Objects appearing multiple times are serialized once
- Circular references are handled correctly
- Per-field
fory:"ref"tags take effect - Adds overhead for tracking object identity
When disabled (default):
- Each object occurrence is serialized independently
- Circular references cause stack overflow or max depth error
- Per-field
fory:"ref"tags are ignored - Better performance for simple data structures
Use reference tracking when:
- Data contains circular references
- Same object is referenced multiple times
- Serializing graph structures (trees with parent pointers, linked lists with cycles)
See References for details.
WithCompatible
Compatible mode is enabled by default in both xlang and native mode. Set
WithCompatible(false) only when every reader and writer always uses the same schema and you want
faster serialization and smaller size:
f := fory.New(fory.WithCompatible(false))
When enabled:
- Type metadata is written to serialized data
- Supports adding/removing fields between versions
- Field names or ids are used for matching (order-independent)
- Larger serialized output due to metadata
When disabled:
- Faster serialization and smaller size
- Fields matched by sorted order
- Requires consistent struct definitions across all services
For xlang payloads, use WithCompatible(false) only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. See Schema Evolution for details.
WithMaxDepth
Set the maximum nesting depth to prevent stack overflow:
f := fory.New(fory.WithMaxDepth(30))
- Default: 20
- Protects against deeply nested, recursive structures or malicious data
- Serialization fails with error when exceeded
WithMaxTypeFields
Set the maximum fields accepted in one received remote struct metadata body:
f := fory.New(fory.WithMaxTypeFields(512))
WithMaxTypeMetaBytes
Set the maximum encoded body bytes accepted for one received TypeDef body, excluding the 8-byte header and any extended-size varint:
f := fory.New(fory.WithMaxTypeMetaBytes(4096))
WithMaxSchemaVersionsPerType
Set the maximum accepted remote metadata versions for one logical type:
f := fory.New(fory.WithMaxSchemaVersionsPerType(10))
WithMaxAverageSchemaVersionsPerType
Set the average accepted remote metadata versions across accepted remote types.
The effective global floor is 8192 schemas:
f := fory.New(fory.WithMaxAverageSchemaVersionsPerType(3))
WithXlang
Select the wire mode:
native := fory.New(fory.WithXlang(false))
xlang := fory.New(fory.WithXlang(true))
When enabled:
- Uses cross-language type system
- Compatible with Java, Python, C++, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin
- Type IDs follow xlang specification
When disabled:
- Go-native serialization mode
- Supports more Go-native type behavior
- Not compatible with other language implementations
Thread Safety
The default Fory instance is NOT thread-safe. For concurrent use, use the thread-safe wrapper:
import "github.com/apache/fory/go/fory/threadsafe"
// Create thread-safe Fory with same options
f := threadsafe.New(
fory.WithXlang(true),
fory.WithTrackRef(true),
)
// Safe for concurrent use from multiple goroutines
go func() {
data, _ := f.Serialize(value1)
// data is already copied, safe to use after return
}()
go func() {
data, _ := f.Serialize(value2)
}()
The thread-safe wrapper:
- Uses
sync.Poolinternally for efficient instance reuse - Automatically copies serialized data before returning
- Accepts the same configuration options as
fory.New()
Global Thread-Safe Instance
For convenience, the threadsafe package provides global functions:
import "github.com/apache/fory/go/fory/threadsafe"
// Uses a global thread-safe instance with default configuration
data, err := threadsafe.Marshal(&myValue)
err = threadsafe.Unmarshal(data, &result)
See Thread Safety for details.
Buffer Management
Zero-Copy Behavior
The default Fory instance reuses its internal buffer:
f := fory.New(fory.WithXlang(true))
data1, _ := f.Serialize(value1)
// WARNING: data1 becomes invalid after next Serialize call!
data2, _ := f.Serialize(value2)
// data1 now points to invalid memory
// To keep the data, copy it:
safeCopy := make([]byte, len(data1))
copy(safeCopy, data1)
The thread-safe wrapper automatically copies data, so this is not a concern:
f := threadsafe.New(fory.WithXlang(true))
data1, _ := f.Serialize(value1)
data2, _ := f.Serialize(value2)
// Both data1 and data2 are valid
Manual Buffer Control
For high-throughput scenarios, you can manage buffers manually:
f := fory.New(fory.WithXlang(true))
buf := fory.NewByteBuffer(nil)
// Serialize to existing buffer
err := f.SerializeTo(buf, value)
// Get serialized data
data := buf.GetByteSlice(0, buf.WriterIndex())
// Process data...
// Reset for next use
buf.Reset()
Configuration Examples
Simple Xlang Data
For simple structs without circular references:
f := fory.New(fory.WithXlang(true))
type Config struct {
Host string
Port int32
}
f.RegisterStruct(Config{}, 1)
data, _ := f.Serialize(&Config{Host: "localhost", Port: 8080})
Graph Structures
For data with circular references:
f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true))
type Node struct {
Value int32
Next *Node `fory:"ref"`
}
f.RegisterStruct(Node{}, 1)
n1 := &Node{Value: 1}
n2 := &Node{Value: 2}
n1.Next = n2
n2.Next = n1 // Circular reference
data, _ := f.Serialize(n1)
Schema Evolution
For data that may evolve over time:
// V1: original struct
type UserV1 struct {
ID int64
Name string
}
// V2: added Email field
type UserV2 struct {
ID int64
Name string
Email string // New field
}
// Serialize with V1 in native mode. Compatible mode is the default.
f1 := fory.New(fory.WithXlang(false))
f1.RegisterStruct(UserV1{}, 1)
data, _ := f1.Serialize(&UserV1{ID: 1, Name: "Alice"})
// Deserialize into V2 - Email will have zero value
f2 := fory.New(fory.WithXlang(false))
f2.RegisterStruct(UserV2{}, 1)
var user UserV2
f2.Deserialize(data, &user)
High-Performance Concurrent
For concurrent high-throughput scenarios:
type Request struct {
ID int64
Payload string
}
f := threadsafe.New(
fory.WithXlang(true),
fory.WithMaxDepth(30),
)
f.RegisterStruct(Request{}, 1)
// Process requests concurrently
for req := range requests {
go func(r Request) {
data, _ := f.Serialize(&r)
sendResponse(data)
}(req)
}
Best Practices
-
Reuse Fory instances: Creating a Fory instance involves initialization overhead. Create once and reuse.
-
Use thread-safe wrapper for concurrency: Never share a non-thread-safe Fory instance across goroutines.
-
Enable reference tracking only when needed: It adds overhead for tracking object identity.
-
Copy serialized data if keeping it: With the default Fory, the returned byte slice is invalidated on the next operation.
-
Set appropriate max depth: Increase for deeply nested structures, but be aware of memory usage.
-
Keep compatible mode for evolving schemas: Use the default when struct definitions may change between service versions.
Security
Security-related configuration:
- Register only the expected structs before deserializing untrusted data.
- Use
WithMaxDepth(...)to reject unexpectedly deep payloads. - Keep the remote schema metadata limits at their defaults unless the data is not malicious and a trusted peer sends larger metadata or many schema versions.
- Prefer concrete struct fields over broad
anyor interface-typed fields for untrusted input.