Skip to main content
Version: dev

Cross-Language Serialization

Fory Go enables seamless data exchange with Java, Python, C++, Rust, and JavaScript. This guide covers cross-language compatibility and type mapping.

Enabling Cross-Language Mode

Cross-language (xlang) mode must be explicitly enabled:

f := fory.New(fory.WithXlang(true), fory.WithCompatible(true))

Type Registration for Cross-Language

Use consistent type IDs across all languages:

Go

type User struct {
ID int64
Name string
}

f := fory.New(fory.WithXlang(true), fory.WithCompatible(true))
f.RegisterStruct(User{}, 1)
data, _ := f.Serialize(&User{ID: 1, Name: "Alice"})

Java

public class User {
public long id;
public String name;
}
Fory fory = Fory.builder().withXlang(true).withCompatible(true).build();
fory.register(User.class, 1);
User user = fory.deserialize(data, User.class);

Python

from dataclasses import dataclass
import pyfory

@dataclass
class User:
id: pyfory.Int64
name: str

fory = pyfory.Fory(xlang=True, compatible=True)
fory.register(User, type_id=1)
user = fory.deserialize(data)

Type Mapping

See Type Mapping Specification for detailed type mappings across all languages.

Field Ordering

Cross-language serialization requires consistent field ordering. Fory sorts fields by their snake_case names alphabetically.

Go field names are converted to snake_case for sorting:

type Example struct {
UserID int64 // -> user_id
FirstName string // -> first_name
Age int32 // -> age
}

// Sorted order: age, first_name, user_id

Ensure other languages use matching field names that produce the same snake_case ordering, or use field IDs for explicit control:

type Example struct {
UserID int64 `fory:"id=0"`
FirstName string `fory:"id=1"`
Age int32 `fory:"id=2"`
}

Examples

Go to Java

Go (Serializer):

type Order struct {
ID int64
Customer string
Total float64
Items []string
}

f := fory.New(fory.WithXlang(true), fory.WithCompatible(true))
f.RegisterStruct(Order{}, 1)

order := &Order{
ID: 12345,
Customer: "Alice",
Total: 99.99,
Items: []string{"Widget", "Gadget"},
}
data, _ := f.Serialize(order)
// Send 'data' to Java service

Java (Deserializer):

public class Order {
public long id;
public String customer;
public double total;
public List<String> items;
}

Fory fory = Fory.builder().withXlang(true).withCompatible(true).build();
fory.register(Order.class, 1);

Order order = fory.deserialize(data, Order.class);

Python to Go

Python (Serializer):

from dataclasses import dataclass
import pyfory

@dataclass
class Message:
id: pyfory.Int64
content: str
timestamp: pyfory.Int64

fory = pyfory.Fory(xlang=True, compatible=True)
fory.register(Message, type_id=1)

msg = Message(id=1, content="Hello from Python", timestamp=1234567890)
data = fory.serialize(msg)

Go (Deserializer):

type Message struct {
ID int64
Content string
Timestamp int64
}

f := fory.New(fory.WithXlang(true), fory.WithCompatible(true))
f.RegisterStruct(Message{}, 1)

var msg Message
f.Deserialize(data, &msg)
fmt.Println(msg.Content) // "Hello from Python"

Nested Structures

Cross-language nested structures require all types to be registered:

Lists and Dense Arrays

Go slices are ordinary list<T> carriers unless a field tag explicitly requests the dense array<T> schema. Use array<T> only for one-dimensional bool or numeric data.

Fory schemaGo carrier and tag sketch
list<int32>[]int32 / fory:"type=list(element=int32)"
array<bool>[]bool / fory:"type=array(element=bool)"
array<int8>[]int8 / fory:"type=array(element=int8)"
array<int16>[]int16 / fory:"type=array(element=int16)"
array<int32>[]int32 / fory:"type=array(element=int32)"
array<int64>[]int64 / fory:"type=array(element=int64)"
array<uint8>[]uint8 / fory:"type=array(element=uint8)"
array<uint16>[]uint16 / fory:"type=array(element=uint16)"
array<uint32>[]uint32 / fory:"type=array(element=uint32)"
array<uint64>[]uint64 / fory:"type=array(element=uint64)"
array<float16>[]float16.Float16 / type=array(element=float16)
array<bfloat16>[]bfloat16.BFloat16 / type=array(element=bfloat16)
array<float32>[]float32 / fory:"type=array(element=float32)"
array<float64>[]float64 / fory:"type=array(element=float64)"

Go:

type Address struct {
Street string
City string
Country string
}

type Company struct {
Name string
Address Address
}

f := fory.New(fory.WithXlang(true), fory.WithCompatible(true))
f.RegisterStruct(Address{}, 1)
f.RegisterStruct(Company{}, 2)

Java:

public class Address {
public String street;
public String city;
public String country;
}

public class Company {
public String name;
public Address address;
}

fory.register(Address.class, 1);
fory.register(Company.class, 2);

Common Issues

Field Name Mismatch

Go uses PascalCase, other languages may use camelCase or snake_case. Fields are matched by their snake_case conversion:

// Go
type User struct {
FirstName string // -> first_name
}

// Java - field name converted to snake_case must match
public class User {
public String firstName; // -> first_name (matches)
}

Type Interpretation

Go unsigned types map to Java signed types with the same bit pattern:

var value uint64 = 18446744073709551615  // Max uint64

Java's long holds the same bits but interprets as -1. Use Long.toUnsignedString() in Java if unsigned interpretation is needed.

Nil vs Null

Go nil slices/maps serialize differently based on configuration:

var slice []string = nil
// In xlang mode: serializes based on nullable configuration

Ensure other languages handle null appropriately.

Best Practices

  1. Use consistent type IDs: Same numeric ID for the same type across all languages
  2. Register all types: Including nested struct types
  3. Match field ordering: Use same snake_case names or explicit field IDs
  4. Test cross-language: Run integration tests early and often
  5. Handle type differences: Be aware of signed/unsigned interpretation differences