bytes can be deserialized by other languages

June 20, 2026 ยท View on GitHub

This page demonstrates common cross-language serialization patterns. Data serialized in one supported language can be deserialized in any other supported language when peers use matching type identity, field schema, and compatibility settings.

Remote Schema Metadata Limits

Compatible mode may receive remote metadata (TypeDef or TypeMeta) for types that are not already known by the reader. Fory limits how many distinct remote metadata versions can be accepted, and also limits the size of each received metadata body:

  • maxSchemaVersionsPerType: maximum accepted remote metadata versions for one logical type. The default is 10.
  • maxAverageSchemaVersionsPerType: average accepted remote metadata versions across all accepted remote types. The default is 3; the effective global floor is 8192 metadata entries.
  • maxTypeFields: maximum fields declared by one received struct metadata body. The default is 512.
  • maxTypeMetaBytes: maximum encoded metadata body bytes for one received TypeDef or TypeMeta body, excluding the 8-byte header and any extended-size varint. The default is 4096.

These limits are resource protections. They do not change wire format, registration requirements, dynamic type loading, unknown-type handling, or schema-evolution compatibility.

Raise these values only when the data is not malicious and a trusted peer sends larger metadata or many schema versions.

LanguageField-count optionMetadata-bytes optionPer-type optionAverage option
JavawithMaxTypeFieldswithMaxTypeMetaByteswithMaxSchemaVersionsPerTypewithMaxAverageSchemaVersionsPerType
ScalawithMaxTypeFieldswithMaxTypeMetaByteswithMaxSchemaVersionsPerTypewithMaxAverageSchemaVersionsPerType
KotlinwithMaxTypeFieldswithMaxTypeMetaByteswithMaxSchemaVersionsPerTypewithMaxAverageSchemaVersionsPerType
Pythonmax_type_fieldsmax_type_meta_bytesmax_schema_versions_per_typemax_average_schema_versions_per_type
JavaScript/TypeScriptmaxTypeFieldsmaxTypeMetaBytesmaxSchemaVersionsPerTypemaxAverageSchemaVersionsPerType
C++max_type_fieldsmax_type_meta_bytesmax_schema_versions_per_typemax_average_schema_versions_per_type
GoWithMaxTypeFieldsWithMaxTypeMetaBytesWithMaxSchemaVersionsPerTypeWithMaxAverageSchemaVersionsPerType
Rustmax_type_fieldsmax_type_meta_bytesmax_schema_versions_per_typemax_average_schema_versions_per_type
C#MaxTypeFieldsMaxTypeMetaBytesMaxSchemaVersionsPerTypeMaxAverageSchemaVersionsPerType
SwiftmaxTypeFieldsmaxTypeMetaBytesmaxSchemaVersionsPerTypemaxAverageSchemaVersionsPerType
DartmaxTypeFieldsmaxTypeMetaBytesmaxSchemaVersionsPerTypemaxAverageSchemaVersionsPerType

Serialize Built-in Types

Common types can be serialized automatically without registration: primitive numeric types, string, binary, array, list, map, and more.

Reduced-precision floating-point values are also part of the built-in xlang type system:

  • float16 and array<float16>
  • bfloat16 and array<bfloat16>

Use the language-specific carrier types documented in the type mapping reference. Python uses pyfory.Float16 and pyfory.BFloat16 as annotation markers only; scalar values are native Python float, and dense reduced-precision arrays use pyfory.Float16Array and pyfory.BFloat16Array. Go uses the float16 and bfloat16 packages for scalar, slice, and array carriers; JavaScript uses number for scalar float16 and bfloat16, and dense array carriers BoolArray, Float16Array, and BFloat16Array for the corresponding array<T> schemas. Dart uses double plus Float16Type or Bfloat16Type metadata for scalar fields, and Float16List / Bfloat16List for dense arrays. Java uses @ArrayType on supported reduced-precision carriers for array<float16> / array<bfloat16> schema, while general object arrays stay on the list path; C++, Rust, and C# provide their own dedicated scalar and array carriers.

When compatible=true, a direct struct/class field can evolve between list<T> and array<T> for dense bool/numeric T. Integer list element encodings in the same signedness and width domain match the corresponding dense array element domain. This applies only to the immediate matched field schema. It does not apply to nested collection, map, array, union, or generic positions. A peer list<T?> schema can be read into a local array<T> field when the actual payload has no null elements. If the payload carries a null element or ref-tracked element encoding, reading it into a local array<T> field raises a compatible-read error.

Java

import org.apache.fory.*;
import org.apache.fory.config.*;

import java.util.*;

public class Example1 {
  public static void main(String[] args) {
    Fory fory = Fory.builder().withXlang(true).build();
    List<Object> list = ofArrayList(true, false, "str", -1.1, 1, new int[100], new double[20]);
    byte[] bytes = fory.serialize(list);
    // bytes can be deserialized by other languages
    fory.deserialize(bytes);
    Map<Object, Object> map = new HashMap<>();
    map.put("k1", "v1");
    map.put("k2", list);
    map.put("k3", -1);
    bytes = fory.serialize(map);
    // bytes can be deserialized by other languages
    fory.deserialize(bytes);
  }
}

Python

import pyfory
import numpy as np

fory = pyfory.Fory(xlang=True)
object_list = [True, False, "str", -1.1, 1,
               np.full(100, 0, dtype=np.int32), np.full(20, 0.0, dtype=np.double)]
data = fory.serialize(object_list)
# bytes can be deserialized by other languages
new_list = fory.deserialize(data)
object_map = {"k1": "v1", "k2": object_list, "k3": -1}
data = fory.serialize(object_map)
# bytes can be deserialized by other languages
new_map = fory.deserialize(data)
print(new_map)

Go

package main

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

func main() {
  list := []any{true, false, "str", -1.1, 1, make([]int32, 10), make([]float64, 20)}
  fory := forygo.NewFory(forygo.WithXlang(true))
  bytes, err := fory.Marshal(list)
  if err != nil {
    panic(err)
  }
  var newValue any
  // bytes can be deserialized by other languages
  if err := fory.Unmarshal(bytes, &newValue); err != nil {
    panic(err)
  }
  fmt.Println(newValue)
  dict := map[string]any{
    "k1": "v1",
    "k2": list,
    "k3": -1,
  }
  bytes, err = fory.Marshal(dict)
  if err != nil {
    panic(err)
  }
  // bytes can be deserialized by other languages
  if err := fory.Unmarshal(bytes, &newValue); err != nil {
    panic(err)
  }
  fmt.Println(newValue)
}

JavaScript

import Fory from "@apache-fory/core";

const fory = new Fory();
const input = fory.serialize("hello fory");
const result = fory.deserialize(input);
console.log(result);

Rust

use fory::Fory;

fn run() {
    let fory = Fory::builder().xlang(true).build();
    let bin = fory.serialize(&"hello".to_string()).expect("serialize success");
    let obj: String = fory.deserialize(&bin).expect("deserialize success");
    assert_eq!("hello".to_string(), obj);
}

Serialize Custom Types

User-defined types must be registered using the register API to establish the mapping relationship between types in different languages. Use consistent type names across all languages.

Java

import org.apache.fory.*;
import org.apache.fory.config.*;
import java.util.*;

public class Example2 {
  public static class SomeClass1 {
    Object f1;
    Map<Byte, Integer> f2;
  }

  public static class SomeClass2 {
    Object f1;
    String f2;
    List<Object> f3;
    Map<Byte, Integer> f4;
    Byte f5;
    Short f6;
    Integer f7;
    Long f8;
    Float f9;
    Double f10;
    short[] f11;
    List<Short> f12;
  }

  public static Object createObject() {
    SomeClass1 obj1 = new SomeClass1();
    obj1.f1 = true;
    obj1.f2 = ofHashMap((byte) -1, 2);
    SomeClass2 obj = new SomeClass2();
    obj.f1 = obj1;
    obj.f2 = "abc";
    obj.f3 = ofArrayList("abc", "abc");
    obj.f4 = ofHashMap((byte) 1, 2);
    obj.f5 = Byte.MAX_VALUE;
    obj.f6 = Short.MAX_VALUE;
    obj.f7 = Integer.MAX_VALUE;
    obj.f8 = Long.MAX_VALUE;
    obj.f9 = 1.0f / 2;
    obj.f10 = 1 / 3.0;
    obj.f11 = new short[]{(short) 1, (short) 2};
    obj.f12 = ofArrayList((short) -1, (short) 4);
    return obj;
  }

  // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.Example2"
  public static void main(String[] args) {
    Fory fory = Fory.builder().withXlang(true).build();
    fory.register(SomeClass1.class, "example.SomeClass1");
    fory.register(SomeClass2.class, "example.SomeClass2");
    byte[] bytes = fory.serialize(createObject());
    // bytes can be deserialized by other languages
    System.out.println(fory.deserialize(bytes));
  }
}

Python

from dataclasses import dataclass
from typing import List, Dict, Any
import pyfory, array


@dataclass
class SomeClass1:
    f1: Any
    f2: Dict[pyfory.Int8, pyfory.Int32]


@dataclass
class SomeClass2:
    f1: Any = None
    f2: str = None
    f3: List[str] = None
    f4: Dict[pyfory.Int8, pyfory.Int32] = None
    f5: pyfory.Int8 = None
    f6: pyfory.Int16 = None
    f7: pyfory.Int32 = None
    # int type will be taken as `pyfory.Int64`.
    # use `pyfory.Int32` for type hint if peer uses more narrow type.
    f8: int = None
    f9: pyfory.Float32 = None
    # float type will be taken as `pyfory.Float64`
    f10: float = None
    f11: pyfory.Array[pyfory.Int16] = None
    f12: List[pyfory.Int16] = None


if __name__ == "__main__":
    f = pyfory.Fory(xlang=True)
    f.register_type(SomeClass1, name="example.SomeClass1")
    f.register_type(SomeClass2, name="example.SomeClass2")
    obj1 = SomeClass1(f1=True, f2={-1: 2})
    obj = SomeClass2(
        f1=obj1,
        f2="abc",
        f3=["abc", "abc"],
        f4={1: 2},
        f5=2 ** 7 - 1,
        f6=2 ** 15 - 1,
        f7=2 ** 31 - 1,
        f8=2 ** 63 - 1,
        f9=1.0 / 2,
        f10=1 / 3.0,
        f11=array.array("h", [1, 2]),
        f12=[-1, 4],
    )
    data = f.serialize(obj)
    # bytes can be deserialized by other languages
    print(f.deserialize(data))

Go

package main

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

func main() {
  type SomeClass1 struct {
    F1 any
    F2 map[int8]int32
  }

  type SomeClass2 struct {
    F1  any
    F2  string
    F3  []any
    F4  map[int8]int32
    F5  int8
    F6  int16
    F7  int32
    F8  int64
    F9  float32
    F10 float64
    F11 []int16
    F12 []int16
  }
  serializer := forygo.NewFory(forygo.WithXlang(true))
  if err := serializer.RegisterStructByName(SomeClass1{}, "example.SomeClass1"); err != nil {
    panic(err)
  }
  if err := serializer.RegisterStructByName(SomeClass2{}, "example.SomeClass2"); err != nil {
    panic(err)
  }
  obj1 := &SomeClass1{F1: true, F2: map[int8]int32{-1: 2}}
  obj := &SomeClass2{
    F1:  obj1,
    F2:  "abc",
    F3:  []any{"abc", "abc"},
    F4:  map[int8]int32{1: 2},
    F5:  127,
    F6:  32767,
    F7:  2147483647,
    F8:  9223372036854775807,
    F9:  1.0 / 2,
    F10: 1.0 / 3.0,
    F11: []int16{1, 2},
    F12: []int16{-1, 4},
  }
  bytes, err := serializer.Marshal(obj)
  if err != nil {
    panic(err)
  }
  var newValue any
  // bytes can be deserialized by other languages
  if err := serializer.Unmarshal(bytes, &newValue); err != nil {
    panic(err)
  }
  fmt.Println(newValue)
}

JavaScript

import Fory, { Type } from "@apache-fory/core";

// Describe data structures using JSON schema
const description = Type.struct(
  { typeName: "example.foo" },
  {
    foo: Type.string(),
  },
);
const fory = new Fory();
const { serialize, deserialize } = fory.register(description);
const input = serialize({ foo: "hello fory" });
const result = deserialize(input);
console.log(result);

Rust

use chrono::{NaiveDate, NaiveDateTime};
use fory::{Fory, ForyStruct};
use std::collections::HashMap;

#[test]
fn complex_struct() {
    #[derive(ForyStruct, Debug, PartialEq)]
    struct Animal {
        category: String,
    }

    #[derive(ForyStruct, Debug, PartialEq)]
    struct Person {
        c1: Vec<u8>,  // binary
        c2: Vec<i16>, // primitive array
        animal: Vec<Animal>,
        c3: Vec<Vec<u8>>,
        name: String,
        c4: HashMap<String, String>,
        age: u16,
        op: Option<String>,
        op2: Option<String>,
        date: NaiveDate,
        time: NaiveDateTime,
        c5: f32,
        c6: f64,
    }
    let person: Person = Person {
        c1: vec![1, 2, 3],
        c2: vec![5, 6, 7],
        c3: vec![vec![1, 2], vec![1, 3]],
        animal: vec![Animal {
            category: "Dog".to_string(),
        }],
        c4: HashMap::from([
            ("hello1".to_string(), "hello2".to_string()),
            ("hello2".to_string(), "hello3".to_string()),
        ]),
        age: 12,
        name: "helo".to_string(),
        op: Some("option".to_string()),
        op2: None,
        date: NaiveDate::from_ymd_opt(2025, 12, 12).unwrap(),
        time: NaiveDateTime::from_timestamp_opt(1689912359, 0).unwrap(),
        c5: 2.0,
        c6: 4.0,
    };

    let mut fory = Fory::builder().xlang(true).build();
    fory
        .register_by_name::<Animal>("example.foo2")
        .expect("register Animal");
    fory
        .register_by_name::<Person>("example.foo")
        .expect("register Person");
    let bin = fory.serialize(&person).expect("serialize success");
    let obj: Person = fory.deserialize(&bin).expect("deserialize success");
    assert_eq!(person, obj);
}

Serialize Shared and Circular References

Shared references and circular references can be serialized automatically with no duplicate data or recursion errors. Enable reference tracking to use this feature.

Java

import org.apache.fory.*;
import org.apache.fory.config.*;
import java.util.*;

public class ReferenceExample {
  public static class SomeClass {
    SomeClass f1;
    Map<String, String> f2;
    Map<String, String> f3;
  }

  public static Object createObject() {
    SomeClass obj = new SomeClass();
    obj.f1 = obj;
    obj.f2 = ofHashMap("k1", "v1", "k2", "v2");
    obj.f3 = obj.f2;
    return obj;
  }

  // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.ReferenceExample"
  public static void main(String[] args) {
    Fory fory = Fory.builder()
        .withXlang(true)
        .withRefTracking(true)
        .build();
    fory.register(SomeClass.class, "example.SomeClass");
    byte[] bytes = fory.serialize(createObject());
    // bytes can be deserialized by other languages
    System.out.println(fory.deserialize(bytes));
  }
}

Python

from typing import Dict
import pyfory

class SomeClass:
    f1: "SomeClass"
    f2: Dict[str, str]
    f3: Dict[str, str]

fory = pyfory.Fory(xlang=True, ref=True)
fory.register_type(SomeClass, name="example.SomeClass")
obj = SomeClass()
obj.f2 = {"k1": "v1", "k2": "v2"}
obj.f1, obj.f3 = obj, obj.f2
data = fory.serialize(obj)
# bytes can be deserialized by other languages
print(fory.deserialize(data))

Go

package main

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

func main() {
  type SomeClass struct {
    F1 *SomeClass
    F2 map[string]string
    F3 map[string]string
  }
  fory := forygo.NewFory(forygo.WithXlang(true), forygo.WithTrackRef(true))
  if err := fory.RegisterStruct(SomeClass{}, 65); err != nil {
    panic(err)
  }
  value := &SomeClass{F2: map[string]string{"k1": "v1", "k2": "v2"}}
  value.F3 = value.F2
  value.F1 = value
  bytes, err := fory.Marshal(value)
  if err != nil {
    panic(err)
  }
  var newValue any
  // bytes can be deserialized by other languages
  if err := fory.Unmarshal(bytes, &newValue); err != nil {
    panic(err)
  }
  fmt.Println(newValue)
}

JavaScript

import Fory, { Type } from "@apache-fory/core";

const description = Type.struct("example.foo", {
  foo: Type.string(),
  bar: Type.struct("example.foo").setTrackingRef(true),
});

const fory = new Fory({ ref: true });
const { serialize, deserialize } = fory.register(description);
const data: any = {
  foo: "hello fory",
};
data.bar = data;
const input = serialize(data);
const result = deserialize(input);
console.log(result.bar.foo === result.foo);

Rust

Circular references cannot be implemented in Rust due to ownership restrictions.

See Also