Skip to main content
Version: 1.2.0

Native Serialization

Go native serialization is the Go-only wire mode selected with fory.WithXlang(false). Use it when every writer and reader is a Go service and the payload should follow Go's type system instead of the portable xlang type system.

Use Xlang Serialization, the default Go mode, when bytes must be read by Java, Python, C++, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Go Fory implementation.

When To Use Native Serialization

Use native serialization when:

  • A payload is produced and consumed only by Go applications.
  • The data model uses Go-specific behavior such as native int/uint, nil slices, nil maps, pointers, interfaces, or Go-only dynamic values.
  • You want faster serialization and smaller size, and every reader uses the same struct schema as the writer.
  • You want compatible schema evolution for Go-only rolling deployments without committing to a cross-language type mapping.
  • You are using reflection or code-generated serializers for Go structs that never leave Go.

Create a Native-Mode Fory Instance

package main

import "github.com/apache/fory/go/fory"

type Order struct {
ID int64
Amount float64
}

func main() {
f := fory.New(fory.WithXlang(false))
if err := f.RegisterStruct(Order{}, 100); err != nil {
panic(err)
}

data, err := f.Serialize(&Order{ID: 1, Amount: 42.5})
if err != nil {
panic(err)
}

var decoded Order
if err := f.Deserialize(data, &decoded); err != nil {
panic(err)
}
}

Reuse a configured Fory instance. The default instance owns reusable buffers and is not thread-safe; use the thread-safe wrapper for concurrent goroutines.

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

f := threadsafe.New(fory.WithXlang(false), fory.WithTrackRef(true))
_ = f.RegisterStruct(Order{}, 100)

Schema Evolution

Native serialization defaults to compatible mode. Keep that default when Go-only services roll independently:

writer := fory.New(fory.WithXlang(false))
reader := fory.New(fory.WithXlang(false))

Compatible mode writes schema metadata so readers can tolerate added, removed, or reordered fields when field names or explicit field IDs remain compatible. See Schema Evolution.

For faster serialization and smaller size, set WithCompatible(false) only when every reader and writer always uses the same Go struct schema.

Registration

Register structs before serializing them. Prefer explicit numeric IDs for long-lived payloads:

_ = f.RegisterStruct(Order{}, 100)
_ = f.RegisterStruct(LineItem{}, 101)

Name-based registration is useful when ID coordination is harder:

_ = f.RegisterStructByName(Order{}, "example.Order")

If you register without stable IDs, every writer and reader must make the same registration choices.

Go Object Surface

Native serialization keeps Go data in Go-native form:

  • Primitive numeric types, including Go-native int and uint.
  • Structs with exported fields.
  • Slices, arrays, maps, and Fory sets.
  • Pointers and nil values, including nil slices and maps.
  • Interfaces and dynamic values when registered serializers can resolve their concrete types.
  • Time values such as time.Time and time.Duration.
  • Reflection-based and code-generated serializers.

Use Supported Types for the full type surface and xlang mapping details.

References And Pointers

Enable reference tracking for shared object identity or cycles:

f := fory.New(fory.WithXlang(false), fory.WithTrackRef(true))

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

Disable reference tracking for value-shaped data. It is faster and smaller, but repeated pointers deserialize as independent values and cyclic graphs are unsupported.

Buffer Ownership

The default Fory instance reuses its internal buffer. Copy serialized bytes if they must outlive the next serialization call:

data, _ := f.Serialize(value)
stable := append([]byte(nil), data...)

The thread-safe wrapper copies bytes before returning them. For high-throughput single-threaded code, serialize into a caller-owned ByteBuffer:

buf := fory.NewByteBuffer(nil)
err := f.SerializeTo(buf, value)
data := buf.GetByteSlice(0, buf.WriterIndex())
_ = err
_ = data

Performance Guidelines

  • Reuse Fory or the thread-safe wrapper instead of constructing a Fory instance per request.
  • Use WithCompatible(false) only when every reader and writer always uses the same Go struct schema and wants faster serialization and smaller size.
  • Register structs with explicit numeric IDs.
  • Disable reference tracking unless the graph requires identity or cycles.
  • Use code generation for hot Go structs when reflection overhead matters.
  • Copy returned bytes only when the data must survive the next serialization call.

Native And Xlang Comparison

RequirementUse native serializationUse xlang serialization
Go-only payloadsYesOptional
Non-Go readers or writersNoYes
Go-native int, uint, nil slice/mapYesLimited
Same-schema compact payloadsYesNo
Compatible schema evolution by defaultYesYes
Portable type mapping across languagesNoYes

Troubleshooting

A non-Go implementation cannot read the payload

The writer is using native serialization. Rebuild it with fory.WithXlang(true) and align type registration with every peer.

A rolling deployment fails after a field change

Native serialization defaults to compatible mode. Keep that default when struct definitions can differ.

A nil slice or map changes shape

Use native serialization for Go-only payloads that must preserve Go nil slice/map semantics. Cross-language schemas should model nullability explicitly.

Returned bytes change after another serialization

The default Fory instance reuses its buffer. Copy the byte slice or use threadsafe.New(...).