Skip to main content
Version: dev

Polymorphism and Dynamic Types

Fory Swift supports dynamic serialization for Any, AnyObject, arbitrary application protocol existentials, and supported heterogeneous collections. Any and AnyObject roots use direct convenience APIs. Application protocols and recursively composed carriers select DynamicSerializer explicitly.

Dynamic Roots

let fory = Fory()

let dynamic: Any = Int32(7)
let data = try fory.serialize(dynamic)
let decoded: Any = try fory.deserialize(data)

The equivalent explicit form is with: DynamicSerializer<Any>.self. It is useful when composing serializers, but is unnecessary for an Any root. AnyObject has the same direct root APIs.

A concrete external value passed as Any may use its registered serializer. For typed roots and fields, select a separate serializer explicitly with with:.

Heterogeneous containers compose carrier serializers:

typealias AnyArraySerializer =
ArraySerializer<DynamicSerializer<Any>>

typealias StringAnyMapSerializer = DictionarySerializer<
String,
DynamicSerializer<Any>
>

Use DynamicSerializer<AnyHashable> for an erased dynamic dictionary key.

Use the exact heterogeneous shape for dynamic lists and maps: [Any], [String: Any], [Int32: Any], or [AnyHashable: Any]. For example, write ["a", "b"] as [Any] before storing that heterogeneous list in Any. Keep homogeneous lists and maps in their ordinary concrete types.

Application Protocols

Register each concrete target that may appear. No Fory-specific marker protocol is required:

protocol Animal {
var name: String { get }
}

@ForyStruct
struct Dog: Animal {
var name: String = ""
}

@ForyStruct(target: ThirdParty.Cat.self)
struct CatSerializer {
var name: String
}

let fory = Fory()
try fory.register(Dog.self, id: 100)
try fory.register(CatSerializer.self, id: 101)

let input: any Animal = Dog(name: "Rex")
let data = try fory.serialize(
input,
with: DynamicSerializer<any Animal>.self
)
let output = try fory.deserialize(
data,
with: DynamicSerializer<any Animal>.self
)

Every concrete target must conform to the requested application protocol. A target that is unregistered or does not conform fails deserialization.

Protocol Fields

Application protocol fields are dynamic:

@ForyStruct
struct Zoo {
var featured: any Animal
var animals: [any Animal]
}

Register Zoo and every concrete target that may appear.

Use an optional when the field needs a nil default:

@ForyStruct
struct OptionalZoo {
var featured: (any Animal)? = nil
}

Protocol Root Carriers

Use DynamicSerializer<T> as the child of a root carrier:

typealias AnimalArraySerializer =
ArraySerializer<DynamicSerializer<any Animal>>

let data = try fory.serialize(
animals,
with: AnimalArraySerializer.self
)

let decoded = try fory.deserialize(
data,
with: AnimalArraySerializer.self
)

Optional and map roots compose the same way:

typealias FeaturedSerializer =
OptionalSerializer<DynamicSerializer<any Animal>>

typealias AnimalMapSerializer = DictionarySerializer<
String,
DynamicSerializer<any Animal>
>

Swift's normal Hashable rules still apply to set elements and dictionary keys. Use AnyHashable for erased dynamic keys.

Explicitly Polymorphic Class Nodes

Select DynamicSerializer<Base> when a class-typed field must preserve its concrete subclass:

@ForyField(with: DynamicSerializer<AnimalBase>.self)
var animal: AnimalBase

Register every concrete subclass that may appear.

Dynamic Any Fields

@ForyStruct
struct DynamicHolder {
var value: Any = ForyAnyNullValue()
var list: [Any] = []
var byName: [String: Any] = [:]
var byId: [Int32: Any] = [:]
var byDynamicKey: [AnyHashable: Any] = [:]
}

Concrete Type Registration Still Applies

If dynamic values contain user-defined types, register those concrete types.

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

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

Null Semantics

  • Any null representation: ForyAnyNullValue
  • AnyObject null representation: NSNull
  • AnyHashable dynamic null-key representation: AnyHashable(ForyAnyNullValue())
  • Optional dynamic values map to the corresponding null representation on decode

AnyHashable Keys

AnyHashable keys must wrap values that are both Hashable and supported by Fory dynamic serialization.