Skip to main content

Fory v1.5.0 Released

· 6 min read
Shawn Yang
Apache Fory PMC Chair

The Apache Fory team is pleased to announce the 1.5.0 release. This release includes 25 PRs. See the Install page to get the libraries for your platform.

Highlights

  • Fory JSON is now up to 5× faster than Jackson and 10× faster than Gson.
  • External-type serialization now covers Rust, Dart, Swift, and C#, allowing generated serializers for third-party structural types while preserving normal xlang wire identities and encodings.
  • C# and Dart now support class inheritance, with Dart also supporting intentional omission of inherited private fields.

Faster Fory JSON

Fory 1.5.0 delivers a major performance leap for Fory JSON, with deep optimizations across both serialization and deserialization. It is now up to 5× faster than Jackson and 10× faster than Gson.

RepresentationOperationfory-json ops/secJackson ops/secGson ops/secvs. Jacksonvs. Gson
StringSerialize7,387,4652,049,3681,084,0423.60×6.81×
StringDeserialize2,897,9551,074,885902,7722.70×3.21×
UTF-8 bytesSerialize10,375,4981,868,6141,037,2115.55×10.00×
UTF-8 bytesDeserialize3,077,1581,268,397933,0792.43×3.30×

External-Type Serialization For Rust/Swift/CSharp/Dart

Fory 1.5.0 adds external-type serialization to Rust, Dart, Swift, and C#. Applications can define a local serializer or schema declaration for a third-party structural type that cannot be modified to carry Fory annotations. Fory then reads and writes the target value directly—without requiring a wrapper or intermediate mirror object.

The generated declaration preserves the runtime's normal registration and wire model. In xlang mode, external structs, enums, and unions use the same applicable identities and encodings as directly supported types. Private, opaque, or invariant-bearing targets can still use a custom serializer when structural generation is not appropriate.

In Rust, the local derive names the target type and is selected explicitly for root values:

use fory::{Fory, ForyStruct};

#[derive(ForyStruct)]
#[fory(target = third_party::User)]
struct UserSerializer {
name: String,
age: u32,
}

let mut fory = Fory::builder().xlang(true).build();
fory.register::<UserSerializer>(100)?;
let bytes = fory.serialize_with::<UserSerializer>(&user)?;
let decoded =
fory.deserialize_with::<UserSerializer>(&bytes)?;

Dart generates a serializer from a local target declaration, then registers the third-party target through the generated module:

(target: third_party.User)
abstract final class UserSerializer {
(id: 1)
late final String name;

(id: 2, type: Int32Type())
late final int age;
}

ExternalSerializersForyModule.register(
fory,
third_party.User,
id: 100,
);

Swift registers and selects the external serializer while the application continues to pass ThirdParty.User values:

@ForyStruct(target: ThirdParty.User.self)
struct UserSerializer {
var name: String
var age: UInt32
}

try fory.register(UserSerializer.self, id: 100)
let bytes = try fory.serialize(user, with: UserSerializer.self)
let decoded = try fory.deserialize(bytes, with: UserSerializer.self)

C# source generation uses a local abstract declaration and registers the target type through the normal API:

using Apache.Fory;
using S = Apache.Fory.Schema.Types;

[ForyStruct(Target = typeof(ThirdParty.User))]
internal abstract class UserSerializer
{
[ForyField(1)]
public abstract string Name { get; }

[ForyField(2, Type = typeof(S.Int32))]
public abstract int Age { get; }
}

fory.Register<ThirdParty.User>(100);
byte[] bytes = fory.Serialize(user);

For construction requirements, nested containers, dynamic values, and advanced mappings, see the dedicated guides for Rust, Dart, Swift, and C#.

Class Inheritance for C# and Dart

Fory 1.5.0 adds class inheritance support to C# and Dart. In both runtimes, inherited and child storage is represented as one flattened schema rather than a nested base object, so ordinary field ordering, schema evolution, reference tracking, and graph-memory checks continue to apply to the concrete type.

In C#, annotate every participating class directly because [ForyStruct] is not inherited. Abstract annotated bases provide schema fields for their concrete descendants, while only the concrete derived type is registered:

[ForyStruct]
public abstract class Entity
{
[ForyField(1)]
private long _id;

public long Id => _id;
}

[ForyStruct]
public sealed class User : Entity
{
[ForyField(2)]
public string Name { get; set; } = string.Empty;
}

fory.Register<User>(102);

Dart generation discovers superclass and applied-mixin storage. Public inherited fields need no annotation on the parent, and a concrete child can intentionally omit inherited private fields:

class MessageBase {
int sequence = 0;
String _cache = '';
}

(ignoreInheritedPrivateFields: true)
class TextMessage extends MessageBase {
TextMessage();

String text = '';
}

The generated TextMessage schema contains sequence and text, but not the inherited private _cache. The option does not omit inherited public fields or private fields declared by the child itself.

See C# class inheritance and Dart inheritance for constructor rules, private-field access across packages, mixins, generics, references, and schema compatibility.

Features

Bug Fix

Other Improvements

Full Changelog: https://github.com/apache/fory/compare/v1.4.0...v1.5.0