跳到主要内容
版本:dev

Schema Metadata

This page covers macro-level schema metadata in Swift.

Available Macro Attributes

  • @ForyStruct on struct/class models and external structural serializers
  • @ForyEnum on C-style enum models and external enum serializers
  • @ForyUnion and @ForyCase on associated-value enum models and external union serializers
  • @ForyField(encoding: ...) on numeric fields
  • @ForyField(with: ...) for exact serializer selection
  • @ListField, @ArrayField, @SetField, and @MapField for collection field metadata

External Targets

Use target: when the serializer declaration and serialized value type are different:

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

Equivalent target arguments are available on @ForyEnum and @ForyUnion. See External-Type Serialization for target access and construction requirements.

@ForyField(with:)

An unannotated field implicitly selects its declared type when that type implements Serializer with Target == Self, including an intentional retroactive external conformance. Use with to select a separate serializer for one exact field node:

@ForyStruct
struct Account {
@ForyField(with: UserSerializer.self)
var owner: ThirdParty.User
}

The serializer target must exactly match the declared field type. Optional and whole-carrier nodes select their carrier serializer explicitly:

@ForyField(with: OptionalSerializer<UserSerializer>.self)
var owner: ThirdParty.User?

@ForyField(with: ArraySerializer<UserSerializer>.self)
var users: [ThirdParty.User]

with may be combined with id, but not with encoding, type, or another type selection at the same node.

@ForyField(encoding:)

Use @ForyField to override integer encoding strategy.

@ForyStruct
struct Metrics: Equatable {
@ForyField(encoding: .fixed)
var u32Fixed: UInt32 = 0

@ForyField(encoding: .tagged)
var u64Tagged: UInt64 = 0
}

Supported combinations

Swift typeSupported encoding valuesDefault encoding
Int32.varint, .fixed.varint
UInt32.varint, .fixed.varint
Int64.varint, .fixed, .tagged.varint
UInt64.varint, .fixed, .tagged.varint
Int.varint, .fixed, .tagged.varint
UInt.varint, .fixed, .tagged.varint

Compile-time validation rejects unsupported combinations (for example, Int32 with .tagged).

Nested Collection Field Metadata

Use @ListField, @ArrayField, @SetField, and @MapField when a collection field needs type-specific wire metadata, such as fixed or tagged integer encoding inside a container. Use @ArrayField for dense non-null bool, integer, and floating-point arrays.

@ForyStruct
struct NestedMetrics: Equatable {
@ListField(element: .encoding(.fixed))
var values: [Int32?] = []

@ArrayField(element: .int32())
var denseValues: [Int32] = []

@SetField(element: .encoding(.fixed))
var ids: Set<UInt32?> = []

@MapField(key: .encoding(.fixed), value: .encoding(.tagged))
var byId: [Int32: UInt64] = [:]

@MapField(value: .list(element: .encoding(.fixed)))
var groups: [String: [Int32?]] = [:]

@ListField(element: .with(UserSerializer.self))
var users: [ThirdParty.User] = []

@MapField(
key: .with(KeySerializer.self),
value: .list(element: .with(UserSerializer.self))
)
var usersByKey: [ThirdParty.Key: [ThirdParty.User]] = [:]
}

Non-null List elements with fixed-width signed or unsigned integer metadata are classified and encoded as the matching Fory primitive packed-array type. Set fields stay classified as Fory sets, including fixed-width integer sets.

When the Swift property type is an alias or otherwise needs a full hint, use @ForyField(type:):

typealias MetricsMap = [String: [Int32?]]

@ForyStruct
struct AliasMetrics: Equatable {
@ForyField(type: .map(
key: .string,
value: .list(.int32(nullable: true, encoding: .fixed))
))
var metrics: MetricsMap = [:]
}

Union payloads use the same DSL through @ForyCase(payload:):

@ForyUnion
enum Event {
@ForyUnknownCase
case unknown(UnknownCase)

@ForyCase(id: 0)
case created(String)

@ForyCase(id: 1, payload: .uint64(encoding: .fixed))
case deleted(UInt64)
}

External payloads select a serializer with .with(...):

@ForyCase(id: 2, payload: .with(UserSerializer.self))
case user(ThirdParty.User)

Every @ForyUnion must declare @ForyUnknownCase case unknown(UnknownCase) and at least one non-unknown case. The unknown case is only the Fory-owned forward-compatibility carrier and cannot be the default value source. It is omitted from the schema case table because the marker only selects the carrier and does not add a schema entry. Schema cases use non-negative IDs.

A known union case has zero or one associated value. Use a struct payload when one alternative contains multiple logical fields.

Model Macro Requirements

Struct and class fields

  • Stored properties must declare explicit types
  • Computed properties are ignored
  • Static/class properties are ignored

Class requirement

Classes annotated with @ForyStruct must provide a required init() for default construction.

@ForyStruct
final class Node {
var value: Int32 = 0
var next: Node? = nil

required init() {}
}

An external class serializer uses a class declaration. Its target must expose an accessible zero-argument initializer and writable matching fields so Fory can preserve shared and circular references.

Dynamic Any Fields in Macro Types

Fory model macros support dynamic fields and nested containers:

  • Any, AnyObject, and arbitrary any Protocol existentials
  • AnyHashable
  • [Any]
  • [String: Any]
  • [Int32: Any]
  • [AnyHashable: Any]

Other dictionary key types work when the key is Hashable and implements Serializer with Target == Self. For an external key using a separate serializer, select it with @MapField(key: .with(KeySerializer.self)).