Skip to main content
Version: 1.7

Basic Serialization

This page covers object graph serialization and core API usage in the default xlang mode for Fory Swift.

Object Graph Serialization​

Use @ForyStruct, @ForyEnum, or @ForyUnion, register types, then serialize and deserialize.

import Foundation
import Fory

@ForyStruct
struct Address: Equatable {
var street: String = ""
var zip: Int32 = 0
}

@ForyStruct
struct Person: Equatable {
var id: Int64 = 0
var name: String = ""
var nickname: String? = nil
var tags: Set<String> = []
var scores: [Int32] = []
var addresses: [Address] = []
var metadata: [Int8: Int32?] = [:]
}

let fory = Fory()
try fory.register(Address.self, id: 100)
try fory.register(Person.self, id: 101)

let person = Person(
id: 42,
name: "Alice",
nickname: nil,
tags: ["swift", "xlang"],
scores: [10, 20, 30],
addresses: [Address(street: "Main", zip: 94107)],
metadata: [1: 100, 2: nil]
)

let data = try fory.serialize(person)
let decoded: Person = try fory.deserialize(data)
assert(decoded == person)

Working with Existing Buffers​

Append serialized bytes to an existing Data and deserialize from ByteBuffer.

var output = Data()
try fory.serialize(person, to: &output)

let inputBuffer = ByteBuffer(data: output)
let fromBuffer: Person = try fory.deserialize(from: inputBuffer)
assert(fromBuffer == person)

Selecting a Serializer​

A type that implements Serializer with Target == Self selects itself:

let data = try fory.serialize(person)
let decoded: Person = try fory.deserialize(data)

This implicit selection composes through generated fields and ordinary optionals, arrays, sets, and dictionaries. It also applies when an application intentionally gives an external type one retroactive self-target conformance.

When a separate serializer targets the value, select it with with:

try fory.register(UserSerializer.self, id: 200)

let data = try fory.serialize(
externalUser,
with: UserSerializer.self
)
let decoded = try fory.deserialize(
data,
with: UserSerializer.self
)

The same selection works with existing buffers:

var output = Data()
try fory.serialize(
externalUser,
with: UserSerializer.self,
to: &output
)

let input = ByteBuffer(data: output)
let decoded = try fory.deserialize(
from: input,
with: UserSerializer.self
)

See External-Type Serialization for structural serializers and recursive carrier roots. See Custom Serializers for serializers implemented directly by a type, retroactive conformances, and separate custom serializers.

Built-in Supported Types​

Primitive and scalar​

  • Bool
  • Int8, Int16, Int32, Int64, Int
  • UInt8, UInt16, UInt32, UInt64, UInt
  • Float, Double
  • String
  • Data

Int and UInt keep 64-bit wire encodings on every platform. When a decoded value is outside the native range on a 32-bit target, deserialization throws ForyError.invalidData.

Date and time​

  • Date
  • LocalDate
  • Duration

Use Date for timestamp values and LocalDate for day-only dates. LocalDate supports epoch-day and Date conversions through fromEpochDay(_:), toEpochDay(), init(utcDate:), and toUTCDate().

Collections​

  • Optionals and arrays whose values directly implement Serializer
  • Sets whose elements directly implement Serializer and are Hashable
  • Dictionaries whose keys and values directly implement Serializer, with Hashable keys

Children that use a separate serializer compose with:

  • OptionalSerializer<S>
  • ArraySerializer<S>
  • SetSerializer<S>
  • DictionarySerializer<KS, VS>

Dynamic​

  • Any and AnyObject
  • AnyHashable
  • Arbitrary application protocol values
  • Supported heterogeneous arrays and dictionaries

Any and AnyObject roots use direct root APIs. Arbitrary application protocol roots and dynamic values nested in carriers use explicit with: selection. See Polymorphism and Dynamic Types.

Cross-Language Interoperability​

The default xlang format is shared by all Fory implementations. The following sections cover its cross-language type mapping, type identity, and interoperability requirements.

Fory Swift can exchange payloads with other Fory implementations using the xlang protocol.

let fory = Fory()

Register Types with Shared Identity​

ID-based registration​

@ForyStruct
struct Order {
var id: Int64 = 0
var amount: Double = 0
}

let fory = Fory()
try fory.register(Order.self, id: 100)

Name-based registration​

try fory.register(Order.self, name: "com.example.Order")

Xlang Rules​

  • Keep type registration mapping consistent across languages
  • Keep compatible mode enabled when independently evolving schemas. Swift enables it by default.
  • Register all user-defined concrete targets used by dynamic fields and application protocol values
  • Use an external structural serializer, a separate custom serializer, or one intentional retroactive self-target conformance for a type owned by another module

Lists and Dense Arrays​

Swift Array<T> fields map to Fory list<T> unless field metadata explicitly requests dense array<T>. Use array<T> only for one-dimensional bool or numeric data.

Fory schemaSwift field metadata sketch
list<int32>@ListField(element: .int32()) var ids: [Int32]
array<bool>@ArrayField(element: .bool) var flags: [Bool]
array<int8>@ArrayField(element: .int8) var values: [Int8]
array<int16>@ArrayField(element: .int16) var values: [Int16]
array<int32>@ArrayField(element: .int32()) var values: [Int32]
array<int64>@ArrayField(element: .int64()) var values: [Int64]
array<uint8>@ArrayField(element: .uint8) var values: [UInt8]
array<uint16>@ArrayField(element: .uint16) var values: [UInt16]
array<uint32>@ArrayField(element: .uint32()) var values: [UInt32]
array<uint64>@ArrayField(element: .uint64()) var values: [UInt64]
array<float16>@ArrayField(element: .float16) var values: [Float16]
array<bfloat16>@ArrayField(element: .bfloat16) var values: [BFloat16]
array<float32>@ArrayField(element: .float32) var values: [Float]
array<float64>@ArrayField(element: .float64) var values: [Double]

An array that uses a separate element serializer still uses normal list encoding. Use @ArrayField only for supported dense bool or numeric arrays.

External Targets​

External structural serializers produce the same xlang STRUCT, ENUM, or UNION schema and value bytes as an equivalent ordinary Swift model:

@ForyStruct(target: ThirdParty.Order.self)
struct OrderSerializer {
var id: Int64
var amount: Double
}

try fory.register(OrderSerializer.self, id: 100)

Use .with(...) in field metadata and with: at a root. See External-Type Serialization.

That explicit selection is required because the structural serializer is a separate declaration. An external type with one intentional retroactive Target == Self conformance instead uses ordinary roots, fields, and carriers.

Swift has no native serialization mode. A known @ForyUnion case has zero or one associated value. Use a struct payload for a union alternative with multiple logical fields.

Swift IDL Workflow​

Generate Swift models directly from Fory IDL/Proto/FBS inputs:

foryc schema.fdl --swift_out ./Sources/Generated

Generated Swift code includes:

  • @ForyStruct, @ForyEnum, @ForyUnion, and field/case metadata
  • Tagged union enums (associated-value enum cases)
  • ForyModule.install(_:) helpers with transitive import installation
  • toBytes / fromBytes helpers on generated types

Install the generated module before xlang serialization:

let fory = Fory(ref: true)
try Addressbook.ForyModule.install(fory)

let payload = try fory.serialize(book)
let decoded: Addressbook.AddressBook = try fory.deserialize(payload)

Run Swift IDL Integration Tests​

cd integration_tests/idl_tests
./run_swift_tests.sh

This runs Swift roundtrip matrix tests and Java peer roundtrip checks (IDL_PEER_LANG=swift).

Debugging Xlang Tests​

Enable debug output when running xlang tests:

ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest

First round trip​

import Fory

@ForyStruct
struct Person: Equatable {
var name: String = ""
var age: Int32 = 0
}

let fory = Fory()
fory.register(Person.self, id: 1)

let person = Person(name: "chaokunyang", age: 28)
let data = try fory.serialize(person)
let result: Person = try fory.deserialize(data)

print("\(result.name) \(result.age)")

For more cross-language rules and examples, see: