跳到主要内容
版本:0.17

引用处理

Fory Go 支持引用跟踪,可处理循环引用和共享对象。 This is essential for serializing complex data structures like graphs, trees with parent pointers, and linked lists with cycles.

启用引用跟踪​

Reference tracking is disabled by default. Enable it when creating a Fory instance:

f := fory.New(fory.WithTrackRef(true))

Important: Global reference tracking must be enabled for any reference tracking to occur. When WithTrackRef(false) (the default), all per-field reference tags are ignored.

引用跟踪工作原理​

Without Reference Tracking (Default)​

When disabled, each object is serialized independently:

f := fory.New() // TrackRef disabled by default

shared := &Data{Value: 42}
container := &Container{A: shared, B: shared}

data, _ := f.Serialize(container)
// 'shared' is serialized TWICE (no deduplication)

With Reference Tracking​

When enabled, objects are tracked by identity:

f := fory.New(fory.WithTrackRef(true))

shared := &Data{Value: 42}
container := &Container{A: shared, B: shared}

data, _ := f.Serialize(container)
// 'shared' is serialized ONCE, second occurrence is a reference

引用标记​

Fory uses flags to indicate reference states during serialization:

FlagValueMeaning
NullFlag-3Nil/null value
RefFlag-2Reference to previously serialized object
NotNullValueFlag-1Non-null value (data follows)
RefValueFlag0Reference value flag

可引用跟踪的类型​

Only certain types support reference tracking. In xlang mode, the following types can track references:

TypeReference TrackedNotes
*struct (pointer to struct)YesEnable with fory:"ref" tag
any (interface)YesAutomatically tracked
[]T (slices)YesEnable with fory:"ref" tag
map[K]VYesEnable with fory:"ref" tag
*int, *string, etc.NoPointer to primitives excluded
PrimitivesNoValue types
time.Time, time.DurationNoValue types
Arrays ([N]T)NoValue types

字段级引用控制​

By default, reference tracking is disabled for individual fields even when global WithTrackRef(true) is set. You can enable reference tracking for specific fields using the ref struct tag:

type Container struct {
// Enable ref tracking for this field
SharedData *Data `fory:"ref"`

// Explicitly disable ref tracking (same as default)
SimpleData *Data `fory:"ref=false"`
}

Important notes:

  • Per-field tags only take effect when global WithTrackRef(true) is set
  • When global WithTrackRef(false) (default), all field ref tags are ignored
  • Applies to slices, maps, and pointer to struct fields
  • Pointer to primitive types (e.g., *int, *string) cannot use this tag
  • Default is ref=false (no reference tracking per field)

See Struct Tags for more details.

循环引用​

Reference tracking is required for circular data structures:

Circular Linked List​

type Node struct {
Value int32
Next *Node `fory:"ref"`
}

f := fory.New(fory.WithTrackRef(true))
f.RegisterStruct(Node{}, 1)

// Create circular list
n1 := &Node{Value: 1}
n2 := &Node{Value: 2}
n3 := &Node{Value: 3}
n1.Next = n2
n2.Next = n3
n3.Next = n1 // Circular reference back to n1

data, _ := f.Serialize(n1)

var result Node
f.Deserialize(data, &result)
// Circular structure is preserved
// result.Next.Next.Next == &result

Parent-Child Tree​

type TreeNode struct {
Value string
Parent *TreeNode `fory:"ref"`
Children []*TreeNode `fory:"ref"`
}

f := fory.New(fory.WithTrackRef(true))
f.RegisterStruct(TreeNode{}, 1)

root := &TreeNode{Value: "root"}
child1 := &TreeNode{Value: "child1", Parent: root}
child2 := &TreeNode{Value: "child2", Parent: root}
root.Children = []*TreeNode{child1, child2}

data, _ := f.Serialize(root)

var result TreeNode
f.Deserialize(data, &result)
// result.Children[0].Parent == &result

Graph Structures​

type GraphNode struct {
ID int32
Neighbors []*GraphNode `fory:"ref"`
}

f := fory.New(fory.WithTrackRef(true))
f.RegisterStruct(GraphNode{}, 1)

// Create a graph
a := &GraphNode{ID: 1}
b := &GraphNode{ID: 2}
c := &GraphNode{ID: 3}

// Bidirectional connections
a.Neighbors = []*GraphNode{b, c}
b.Neighbors = []*GraphNode{a, c}
c.Neighbors = []*GraphNode{a, b}

data, _ := f.Serialize(a)

var result GraphNode
f.Deserialize(data, &result)

Shared Object Deduplication​

Reference tracking also deduplicates shared objects:

type Config struct {
Setting string
}

type Application struct {
MainConfig *Config `fory:"ref"`
BackupConfig *Config `fory:"ref"`
FallbackConfig *Config `fory:"ref"`
}

f := fory.New(fory.WithTrackRef(true))
f.RegisterStruct(Config{}, 1)
f.RegisterStruct(Application{}, 2)

// Shared configuration
config := &Config{Setting: "value"}

// Multiple references to same object
app := &Application{
MainConfig: config,
BackupConfig: config,
FallbackConfig: config,
}

data, _ := f.Serialize(app)
// 'config' serialized once, others are references

var result Application
f.Deserialize(data, &result)
// result.MainConfig == result.BackupConfig == result.FallbackConfig

Performance Considerations​

Overhead​

Reference tracking adds overhead:

  • Memory for tracking seen objects (hash map)
  • Hash lookups during serialization
  • Additional bytes for reference flags and IDs

When to Enable​

Enable reference tracking when:

  • Data has circular references
  • Same object referenced multiple times
  • Serializing graph structures
  • Object identity must be preserved

Disable reference tracking when:

  • Data is tree-structured (no cycles)
  • Each object appears only once
  • Maximum performance is required
  • Object identity doesn't matter

Memory Usage​

Reference tracking maintains a map of serializing objects:

// Internal reference tracking structure
type RefResolver struct {
writtenObjects map[refKey]int32 // pointer -> reference ID
readObjects []reflect.Value // reference ID -> object
}

For large object graphs, this may increase memory usage.

Error Handling​

Without Reference Tracking​

Circular references without tracking cause stack overflow or max depth errors:

f := fory.New() // No reference tracking

n1 := &Node{Value: 1}
n1.Next = n1 // Self-reference

data, err := f.Serialize(n1)
// Error: max depth exceeded (or stack overflow)

Invalid Reference ID​

During deserialization, an invalid reference ID produces an error:

// Error type: ErrKindInvalidRefId

This occurs when serialized data contains a reference to an object that wasn't previously serialized.

Complete Example​

package main

import (
"fmt"
"github.com/apache/fory/go/fory"
)

type Person struct {
Name string
Friends []*Person `fory:"ref"`
BestFriend *Person `fory:"ref"`
}

func main() {
f := fory.New(fory.WithTrackRef(true))
f.RegisterStruct(Person{}, 1)

// Create people with mutual friendships
alice := &Person{Name: "Alice"}
bob := &Person{Name: "Bob"}
charlie := &Person{Name: "Charlie"}

alice.Friends = []*Person{bob, charlie}
alice.BestFriend = bob

bob.Friends = []*Person{alice, charlie}
bob.BestFriend = alice // Mutual best friends

charlie.Friends = []*Person{alice, bob}

// Serialize
data, err := f.Serialize(alice)
if err != nil {
panic(err)
}
fmt.Printf("Serialized %d bytes\n", len(data))

// Deserialize
var result Person
if err := f.Deserialize(data, &result); err != nil {
panic(err)
}

// Verify circular references preserved
fmt.Printf("Alice's best friend: %s\n", result.BestFriend.Name)
fmt.Printf("Bob's best friend: %s\n", result.BestFriend.BestFriend.Name)
// Output: Alice (circular reference preserved)
}

相关主题​