跳到主要内容
版本:1.5.0

自定义序列化器

当目标类型需要自定义编码,或无法满足外部结构化序列化器的直接访问要求时,请使用自定义序列化器。

自定义序列化器并非只能用于外部类型:

  • 如果目标类型自身遵循 Serializer,并且 Target == Self,则该实现会在所有位置被隐式选中。
  • 如果单独定义的序列化器以另一种类型作为 Target,则必须在每个需要它的位置显式选择该序列化器。

根值、生成字段、可选值、数组、集合和字典都遵循相同的选择规则。请注册单独定义的序列化器,并在使用它的位置显式选择它。

何时使用自定义序列化器

  • 目标类型包含私有或不可变状态。
  • 目标类型必须强制执行构造不变量。
  • 目标类型需要专用的紧凑编码。
  • 目标类型需要自定义校验或构造逻辑。
  • 外部枚举无法进行穷尽式 switch
  • 外部联合类型无法表示 UnknownCase

用户自有的目标类型

如果目标类型由你维护,请让目标类型自身实现 Serializer,并将 Target 设为相同类型:

import Fory

struct AccountID: Serializer, Equatable {
typealias Target = AccountID

let rawValue: UInt64

static var staticTypeId: TypeId {
.ext
}

static func writeData(
_ value: AccountID,
_ context: WriteContext
) throws {
try UInt64.writeData(value.rawValue, context)
}

static func readData(
_ context: ReadContext
) throws -> AccountID {
AccountID(rawValue: try UInt64.readData(context))
}
}

通过常规的根值 API 注册并使用该类型:

let fory = Fory()
try fory.register(AccountID.self, id: 300)

let input = AccountID(rawValue: 42)
let data = try fory.serialize(input)
let output: AccountID = try fory.deserialize(data)

assert(input == output)

由于 AccountID.Target == AccountID,因此不需要 with: 参数。

为外部类型定义一个全局序列化器

Swift 允许应用通过追溯遵循(retroactive conformance)让外部类型实现 Serializer

import Foundation
import Fory

extension UUID: @retroactive Serializer {
public typealias Target = UUID

public static var staticTypeId: TypeId {
.ext
}

public static func defaultValue(
_ context: ReadContext
) throws -> UUID {
_ = context
return UUID(
uuidString: "00000000-0000-0000-0000-000000000000"
)!
}

public static func writeData(
_ value: UUID,
_ context: WriteContext
) throws {
try String.writeData(value.uuidString, context)
}

public static func readData(
_ context: ReadContext
) throws -> UUID {
let raw = try String.readData(context)
guard let uuid = UUID(uuidString: raw) else {
throw ForyError.invalidData("invalid UUID string: \(raw)")
}
return uuid
}
}

注册外部类型本身:

try fory.register(UUID.self, id: 300)

let input = UUID()
let data = try fory.serialize(input)
let output: UUID = try fory.deserialize(data)

由于 UUID.Target == UUID,没有添加注解的生成字段和普通容器也会选用该实现:

@ForyStruct
struct Request {
var requestID: UUID
}

let input = [UUID(), UUID()]
let data = try fory.serialize(input)
let output: [UUID] = try fory.deserialize(data)

追溯遵循会作用于整个进程。对于一个给定类型,Swift 只允许存在一个 Serializer 遵循;@retroactive 只是表明你已知晓编译器警告,并不能让相互冲突的多个遵循变得安全。只有当应用有意选择唯一的全局实现时,才应采用这种形式。公共库通常应改为提供单独定义的序列化器。

单独定义的序列化器

当公共库不应声明作用于整个进程的遵循,或应用需要多个实现或替代实现时,请单独定义序列化器。目标类型既可以是外部类型,也可以是用户自有类型:

import Foundation
import Fory

public enum UUIDStringSerializer: Serializer {
public typealias Target = UUID

public static var staticTypeId: TypeId {
.ext
}

public static func defaultValue(
_ context: ReadContext
) throws -> UUID {
_ = context
return UUID(
uuidString: "00000000-0000-0000-0000-000000000000"
)!
}

public static func writeData(
_ value: UUID,
_ context: WriteContext
) throws {
try String.writeData(value.uuidString, context)
}

public static func readData(
_ context: ReadContext
) throws -> UUID {
let raw = try String.readData(context)
guard let uuid = UUID(uuidString: raw) else {
throw ForyError.invalidData("invalid UUID string: \(raw)")
}
return uuid
}
}

注册单独定义的序列化器,并在根值处显式选择它:

let fory = Fory()
try fory.register(UUIDStringSerializer.self, id: 300)

let input = UUID()
let data = try fory.serialize(input, with: UUIDStringSerializer.self)
let output = try fory.deserialize(data, with: UUIDStringSerializer.self)

assert(input == output)

另一个声明(例如 UUIDBytesSerializer)可以使用不同的主体编码处理同一目标类型。Fory 无法自动在多个单独定义的序列化器中进行选择。请在根值处通过 with: 选择所需的序列化器,并在字段上使用与之匹配的注解。一个 Fory 实例只能为该目标类型注册一种实现。

直接以 AnyAnyObject 作为根值的便捷 API 仍然是动态操作。以 Any 形式传入具体值时,可能会使用已经注册的序列化器,但使用静态类型的根值和字段仍然需要通过 with: 指定单独定义的序列化器。

字段和容器

如果字段类型直接实现了 Serializer,并且 Target == Self,则无需指定选择器:

@ForyStruct
struct Request {
var accountID: AccountID
}

单独定义的序列化器必须显式选择:

@ForyStruct
struct ExternalRequest {
@ForyField(with: UUIDStringSerializer.self)
var requestID: UUID
}

如果普通容器所包含的类型直接实现了 Serializer,则无需指定选择器。这也包括有意添加的追溯遵循:

let accountIDs = [
AccountID(rawValue: 1),
AccountID(rawValue: 2),
]
let data = try fory.serialize(accountIDs)
let output: [AccountID] = try fory.deserialize(data)

如果元素使用单独定义的序列化器,请在容器注解中指定该序列化器:

@ListField(element: .with(UUIDStringSerializer.self))
var requestIDs: [UUID]

在根值处,请使用与之匹配的容器序列化器:

let data = try fory.serialize(
requestIDs,
with: ArraySerializer<UUIDStringSerializer>.self
)

自定义序列化器规则

自定义序列化器的 staticTypeId 必须返回 .ext.structType.enumType.typedUnion 分别保留给 @ForyStruct@ForyEnum@ForyUnion

writeDatareadData 只处理目标值的编码主体。请勿在这两个操作中调用根值的 serializedeserialize 方法。

默认值

仅当目标类型存在可用于空字段或缺失字段的有效值时,才实现 defaultValue(_:)

输入校验

请使用适当的 ForyError 拒绝无效输入。

自定义类序列化器

对于存在循环引用的类,请重写读取完整值的 read 操作,并使用 Fory 的引用 API,以确保重复引用解析为同一个对象。