KeyedCodable 是对 Swift 的 Codable 的补充,专为自动嵌套键映射而设计。 它的目标是避免手动实现 Encodable/Decodable,并使编码/解码更容易、更具可读性、更少的样板代码,最重要的是,与“标准”Codable 完全兼容。
首先,请看 Apple 提供的 Codable 示例。
struct Coordinate {
var latitude: Double
var longitude: Double
var elevation: Double
enum CodingKeys: String, CodingKey {
case latitude
case longitude
case additionalInfo
}
enum AdditionalInfoKeys: String, CodingKey {
case elevation
}
}
extension Coordinate: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
latitude = try values.decode(Double.self, forKey: .latitude)
longitude = try values.decode(Double.self, forKey: .longitude)
let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
}
}
extension Coordinate: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)
var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
try additionalInfo.encode(elevation, forKey: .elevation)
}
}
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
var elevation: Double
enum CodingKeys: String, KeyedKey {
case latitude
case longitude
case elevation = "additionalInfo.elevation"
}
}
默认情况下,点号用作分隔内部键的分隔符
Flat 类特性允许您将属性分组为更小的部分。 在下面的示例中,您可以看到
Location
中{
"longitude": 3.2,
"latitude": 3.4,
"greeting": "hallo"
}
struct InnerWithFlatExample: Codable {
@Flat var location: Location?
let greeting: String
}
struct Location: Codable {
let latitude: Double
let longitude: Double
}
struct InnerWithFlatExample: Codable {
let location: Location?
let greeting: String
enum CodingKeys: String, KeyedKey {
case greeting
case location = ""
}
}
默认情况下,空字符串或空格用于标记 flat 类
您可能发现,如果任何数组元素的解码失败,则解码数组将失败。 Flat 数组 KeyedCodable 的特性会忽略不正确的元素,并创建一个仅包含有效元素的数组。 在下面的示例中,即使第二个元素的解码失败,array
属性也将包含三个元素 [1,3,4]。
{
"array": [
{
"element": 1
},
{},
{
"element": 3
},
{
"element": 4
}
]
}
struct OptionalArrayElementsExample: Codable {
@Flat var array: [ArrayElement]
}
struct ArrayElement: Codable {
let element: Int
}
struct OptionalArrayElementsExample: Codable {
let array: [ArrayElement]
enum CodingKeys: String, KeyedKey {
case array = ".array"
}
}
要启用 flat 数组,您必须在属性名称之前添加 [flat][delimiter] - 默认情况下它是“空字符串 + 点”
如果 json 的键和 KeyedCodable
使用的分隔符之间存在冲突,您可以使用 KeyOptions
来配置映射功能。 您可以通过在 CodingKeys 中提供 options: KeyOptions?
属性来实现(使用 nil
使用默认值)。 您也可以通过设置 .none
值来禁用该功能。
{
"* name": "John",
"": {
".greeting": "Hallo world",
"details": {
"description": "Its nice here"
}
},
"longitude": 3.2,
"latitude": 3.4,
"array": [
{
"element": 1
},
{},
{
"element": 3
},
{
"element": 4
}
],
"* array1": [
{
"element": 1
},
{},
{
"element": 3
},
{
"element": 4
}
]
}
struct KeyOptionsExample: Codable {
let greeting: String
let description: String
let name: String
let location: Location
let array: [ArrayElement]
let array1: [ArrayElement]
enum CodingKeys: String, KeyedKey {
case location = "__"
case name = "* name"
case greeting = "+.greeting"
case description = ".details.description"
case array = "### .array"
case array1 = "### .* array1"
var options: KeyOptions? {
switch self {
case .greeting: return KeyOptions(delimiter: .character("+"), flat: .none)
case .description: return KeyOptions(flat: .none)
case .location: return KeyOptions(flat: .string("__"))
case .array, .array1: return KeyOptions(flat: .string("### "))
default: return nil
}
}
}
}
如果 JSON 中未设置值(根本没有键或设置了空值),则 @Zero 功能会为属性设置默认的“零”值。 如果您发现从业务角度来看 0 和 nil 之间没有区别,您可以轻松地从您的 codable 中删除 optional。
{
"name": "Jan"
}
struct ZeroTestCodable: Codable {
var name: String // Jan
@Zero var secondName: String // "" - empty string
@Zero var numberOfChildren: Int // 0
@Zero var point: Point // Point(x: 0.0, y: 0.0)
}
struct Point: Codable {
let x: Float
let y: Float
}
您是否曾经需要解码 JSON 中的多个日期格式? 或者您是否必须在解码/编码过程中添加从一个值到另一个值的自定义转换,并且您必须编写大量的样板代码进行手动编码? 转换器旨在使自定义转换变得更容易!
要添加您的自定义转换器,您必须实现 DecodableTransformer
、EncodableTransformer
或 Transformer
以进行双向转换。
{
"date": "2012-05-01"
}
struct DateCodableTrasform: Codable {
@CodedBy<DateTransformer> var date: Date
}
enum DateTransformer<Object>: Transformer {
static func transform(from decodable: String) -> Any? {
return formatter.date(from: decodable)
}
static func transform(object: Object) -> String? {
guard let object = object as? Date else { return nil }
return formatter.string(from: object)
}
private static var formatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}
}
您甚至可以为非 Codable 模型实现转换器,并在您的 Codable 树中使用它,如下所示
{
"user": 3
}
struct DetailPage: Codable {
@CodedBy<UserTransformer> var user: User
}
struct User { // User do not implement Codable protocol
let id: Int
}
enum UserTransformer<Object>: Transformer {
static func transform(from decodable: Int) -> Any? {
return User(id: decodable)
}
static func transform(object: Object) -> Int? {
return (object as? User)?.id
}
}
要支持嵌套键映射,您需要为您的 CodingKeys
枚举使用 KeyedKey
而不是 CodingKey
,并使用 KeyedJSONEncoder
/KeyedJSONDecoder
代替标准 JSONEncoder
/JSONDecoder
。 请注意,Keyed 编码器继承自标准等效项,因此它们与 Apple 版本完全兼容。
struct Model: Codable {
var property: Int
}
从字符串解码
let model = try Model.keyed.fromJSON(jsonString)
从数据解码
let model = try Model.keyed.fromJSON(data)
编码为字符串
model.keyed.jsonString()
编码为数据
model.keyed.jsonData()
如果需要其他设置,您可以在方法参数中提供编码器
您也可以像标准版本一样使用 Keyed 编码器。
let model = try KeyedJSONDecoder().decode(Model.self, from: data)
let data = try KeyedJSONEncoder().encode(model)
值得一提的是,Keyed 编码器支持简单类型,例如 String
、Int
等。 例如,当我们尝试使用标准 JSONDecoder
解码 Int
时
let number = try JSONDecoder().decode(Int.self, from: data)
它将抛出一个不正确的格式错误。 Keyed 版本将成功解析。
可以使用标准 JSON 编码器,并且仍然编码/解码 KeyedCodables。 为此,您必须使用 Keyed<>
包装器
let model = try JSONDecoder().decode(Keyed<Model>.self, from: data).value
let data = try JSONEncoder().encode(Keyed(model))
如果您无法访问编码器的初始化代码,这可能会很有用。 在这种情况下,您的模型可能如下所示
struct KeyedModel: Codable {
...
enum CodingKeys: String, KeyedKey {
....
}
....
}
struct Model {
let keyed: Keyed<KeyedModel>
}
如果您需要手动实现 Codable,您可以使用 AnyKey
来更简单地访问嵌套键。
private let TokenKey = AnyKey(stringValue: "data.tokens.access_token")
struct TokenModel: Codable {
let token: Token
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AnyKey.self)
token = try container.decode(Token.self, forKey: TokenKey)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: AnyKey.self)
try container.encode(token, forKey: TokenKey)
}
}
如前所述,可以在 KeyedKey
级别设置键选项(例如分隔符),也可以全局设置。
为此,您需要设置 KeyedConfig.default.keyOptions
的值。
除了键选项之外,还可以设置 Codable 扩展中默认使用的编码器。
3.0.0 版本与 2.x.x 版本向后兼容,但是,您必须对与 @PropertyWrapper
关联的所有新特性使用 swift 5.1。
遗憾的是,2.x.x 版本与 1.x.x 版本不兼容,但我相信新方法更好,并且比以前的版本带来更少的样板代码。 无需添加任何手动映射实现,它非常简单,因此我强烈建议迁移到新版本。 您只需要
KeyedJSONEncoder
\ KeyedJSONDecoder
代替 JSONEncoder
\ JSONDecoder
!!KeyedKey
并在此处移动您的映射KeyedCodable
协议