在 Swift 中使用 Codable
类型的实用助手
将以下内容添加到您的 Package.swift
依赖项中
.package(url: "https://github.com/yonaskolb/Codability.git", from: "0.2.0"),
然后在任何需要的地方导入:import Codability
默认情况下,如果数组或字典中的单个元素失败,Decodable
将抛出错误。InvalidElementStrategy
是一个枚举,可让您控制此行为。它有多种情况
remove
: 从集合中删除元素fail
: 将使整个解码失败。这是解码器使用的默认行为fallback(value)
: 这使您能够以类型安全的方式提供类型化的值custom((EncodingError)-> InvalidElementStrategy)
: 让您根据抛出的特定错误提供动态行为,从而让您查找确切涉及的键解码数组或字典时,请使用 decodeArray
和 decodeDictionary
函数(也有 IfPresent
变体)。
InvalidElementStrategy
可以传递到这些函数中,也可以使用 JSONDecoder().userInfo[.invalidElementStrategy]
设置默认值,否则将使用默认值 fail
。
给定以下 JSON
{
"array": [1, "two", 3],
"dictionary": {
"one": 1,
"two": "two",
"three": 3
}
}
struct Object: Decodable {
let array: [Int]
let dictionary: [String: Int]
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
array = try container.decodeArray([Int].self, forKey: "array", invalidElementStrategy: .fallback(0))
dictionary = try container.decodeDictionary([String: Int].self, forKey: "dictionary", invalidElementStrategy: .remove)
}
}
let decoder = JSONDecoder()
// this will provide a default if none is passed into the decode functions
decoder.userInfo[.invalidElementStrategy] = InvalidElementStrategy<Any>.remove
let decodedObject = try decoder.decode(Object.self, from: json)
decodedObject.array == [1,0,3]
decodedObject.dictionary = ["one": 1, "three": 3]
使用 Codable 的缺点是您无法编码和解码类型混合或未知的属性,例如 [String: Any]
、[Any]
或 Any
。这些在许多 API 中有时是必要的恶,而 AnyCodable
使支持这些类型变得容易。
有 2 种不同的使用方式
这样做的好处是您可以使用合成的 codable 函数。但缺点是这些值必须使用 AnyCodable.value
解包。您可以在对象上添加自定义 setter 和 getter,以使访问这些值更容易
struct AnyContainer: Codable {
let dictionary: [String: AnyCodable]
let array: [AnyCodable]
let value: AnyCodable
}
这使您可以保留正常的结构,但需要使用 decodeAny
或 encodeAny
函数。如果您出于其他原因必须实现自定义 init(from:)
或 encode
函数,这是首选方法。在幕后,它使用 AnyCodable
进行编码,然后在解码的情况下转换为您期望的类型。
struct AnyContainer: Codable {
let dictionary: [String: Any]
let array: [Any]
let value: Any
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
dictionary = try container.decodeAny(.dictionary)
array = try container.decodeAny([Any].self, forKey: .array)
value = try container.decodeAny(Any.self, forKey: .value)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeAny(dictionary, forKey: .dictionary)
try container.encodeAny(array, forKey: .array)
try container.encodeAny(value, forKey: .value)
}
enum CodingKeys: CodingKey {
case dictionary
case value
case array
}
}
RawCodingKey
可用于提供动态编码键。当您仅在一个地方使用这些值时,它还可以消除创建标准 CodingKey
枚举的需要。
struct Object: Decodable {
let int: Int
let bool: Bool
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
int = try container.decode(Int.self, forKey: "int")
bool = try container.decode(Bool.self, forKey: "bool")
}
}
KeyedDecodingContainer
和 UnkeyedDecodingContainer
上的默认解码函数都要求显式传入类型。Codabilty
添加了泛型函数来消除这种需要,使您的 init(from:)
更加简洁。key
参数也变为未命名。
Codabality
提供的所有辅助函数(例如 decodeAny
、decodeArray
或 decodeDictionary
函数)也具有这些泛型变体,包括 IfPresent
。
struct Object: Decodable {
let int: Int?
let bool: Bool
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
// old
int = try container.decodeIfPresent(Int.self, forKey: "int")
bool = try container.decode(Bool.self, forKey: "bool")
// new
int = try container.decodeIfPresent("int")
bool = try container.decode("bool")
}
}
感谢 @mattt 和 Flight-School/AnyCodable 为 AnyCodable
支持奠定了基础。许可证