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()
Default settings:
| Option | Default | Description |
|---|---|---|
| TrackRef | false | Reference tracking disabled |
| MaxDepth | 20 | Maximum nesting depth |
| IsXlang | false | Cross-language mode disabled |
| Compatible | false | Schema evolution mode disabled |
With Options
f := fory.New(
fory.WithTrackRef(true),
fory.WithCompatible(true),
fory.WithMaxDepth(10),
)
Configuration Options
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
Enable compatible mode for schema evolution:
f := fory.New(fory.WithCompatible(true))
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 (default):
- Compact serialization without field metadata
- Faster serialization and smaller output
- Fields matched by sorted order
- Requires consistent struct definitions across all services
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
WithXlang
Enable cross-language serialization mode:
f := fory.New(fory.WithXlang(true))
When enabled:
- Uses cross-language type system
- Compatible with Java, Python, C++, Rust, JavaScript
- Type IDs follow xlang specification
When disabled (default):
- Go-native serialization mode
- Support more Go-native types
- 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.WithTrackRef(true),
fory.WithCompatible(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()
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()
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()
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 Data (Default)
For simple structs without circular references:
f := fory.New()
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.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
f1 := fory.New(fory.WithCompatible(true))
f1.RegisterStruct(UserV1{}, 1)
data, _ := f1.Serialize(&UserV1{ID: 1, Name: "Alice"})
// Deserialize into V2 - Email will have zero value
f2 := fory.New(fory.WithCompatible(true))
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.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.
-
Use compatible mode for evolving schemas: Enable when struct definitions may change between service versions.