Swift 没有 CodingKeys 的自定义可解码初始化程序
Swift custom decodable initializer without CodingKeys
假设我有以下可解码结构作为示例来说明我正在尝试做的事情:
struct Object: Decodable {
var id: String
var name: String
}
和这个 JSON:
[
{
"id": "a",
"name": "test"
},
{
"id": "b",
"name": null
}
]
请注意,名称 属性 有时可以是 null
。由于 json 键与结构 属性 名称匹配,因此这在很大程度上可以正常工作,因此我不需要 CodingKey
枚举,但是 name
属性 有时可以为空。但是,我不想让 name
可选,而是想替换默认字符串,所以我需要一个自定义初始化程序:
struct Object: Decodable {
var id: String
var name: String
init(from decoder: Decoder) throws {
...
self.name = <value from decoder> ?? "default name"
...
}
}
但这需要一个CodingKey
对象。我正在使用默认键。我现在还需要 CodingKey
枚举吗?即使我所有的键都匹配?或者有没有一种方法可以让自定义 Decodable
初始化程序仅使用键原样?
是否有某种我可以使用的默认容器?
let container = try decoder.container(keyedBy: <defaultContainer???>)
我尝试使用这两种变体,但都不起作用:
let container = try decoder.singleValueContainer()
let container = try decoder.unkeyedContainer()
我怎样才能拥有自定义 Decodable
初始化器 同时 使用默认键?
正如对您的问题所做的评论所说,编译器会为您生成一个 CodingKeys
对象。当键与您的枚举名称不匹配时,您可以实施自定义 enum
或 class 尊重您从源接收的 JSON 数据。
您可以这样实现您的对象:
struct TestObject: Codable {
var id: String
var name: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let nameOrNil = try? container.decode(String.self, forKey: .name)
self.id = id
self.name = nameOrNil ?? "Default value"
}
}
container
的 decode(_:forKey:)
方法可能会抛出错误,但如果您使实现解除错误,而不是使用 try?
返回一个可选值,您可以应用 nil只要名称未包含在 JSON.
中,合并运算符就会分配默认值
这里证明:
let json = """
[
{
"id": "a",
"name": "test"
},
{
"id": "b",
"name": null
}
]
""".data(using: .utf8)!
let decodedArray = try JSONDecoder().decode([TestObject].self, from: json)
print(decodedArray)
参考文献:
CodingKeys
的自动生成真的很奇怪。它的范围和可用性根据您拥有的成员而变化。
假设您只有 Decodable
。这些编译:
struct Decodable: Swift.Decodable {
static var codingKeysType: CodingKeys.Type { CodingKeys.self }
}
struct Decodable: Swift.Decodable {
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}
...你可以把它们放在一起,如果你加上 private
.
struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}
…但是让 func
成为一个初始值设定项,同样,没有编译。
struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}
您可以将其更改为完全 Codable
,而不仅仅是 Decodable
…
struct Decodable: Codable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}
但是你不能在类型范围内使用 CodingKeys
,所以 属性 不会编译。
考虑到您可能不需要这样的 属性,只需使用 Codable
,向 Apple 提交错误并引用此答案,希望我们都可以切换到 Decodable
他们修好了。
问题是 CodingKeys
只有在没有完全手动符合相关协议的情况下才会自动为您生成。 (这对于 Objective-C 开发人员来说非常熟悉,如果您手动实现所有相关的访问器方法,属性 的支持 ivar 将不会自动合成。)
因此,在以下情况下,CodingKeys
不会自动为您创建:
您仅采用 Decodable
并实现了您自己的 init(from:)
;
您仅采用 Encodable
并实现了您自己的 encode(to:)
;或
您同时采用了 Encodable
和 Decodable
(或者只是 Codable
)并实现了您自己的 init(from:)
和 encode(to:)
。
所以你的情况属于上面的第一种情况。
有人建议您可以通过采用 Codable
来解决您的难题,即使您只实施了 init(from:)
并且可能不打算使用 Encodable
行为。实际上,这取决于您不打算真正使用的协议的副作用。
这并没有太大关系,采用 Codable
是可行的,但继续定义 CodingKeys
可能被认为“更正确”,而不是依赖于未实现的 Encodable
副作用:
struct Object: Decodable {
var id: String
var name: String
enum CodingKeys: String, CodingKey {
case id, name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = (try? container.decode(String.self, forKey: .name)) ?? "Default Value"
}
}
您可以使用 属性 包装器模拟该行为,但这不是完美的解决方案,而且有点老套。该问题已在 Swift 论坛上多次讨论。
使用 属性 包装器:
struct Object: Decodable {
var id: String
@DecodableDefault var name: String
}
属性 包装器的代码:
public protocol DecodableDefaultValue: Decodable {
static var defaultDecodableValue: Self { get }
}
@propertyWrapper
public struct DecodableDefault<T: Decodable>: Decodable {
public var wrappedValue: T
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(T.self)
}
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
}
extension DecodableDefault: Encodable where T: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
extension DecodableDefault: Equatable where T: Equatable { }
extension DecodableDefault: Hashable where T: Hashable { }
public extension KeyedDecodingContainer {
func decode<T: DecodableDefaultValue>(_: DecodableDefault<T>.Type, forKey key: Key) throws -> DecodableDefault<T> {
guard let value = try decodeIfPresent(DecodableDefault<T>.self, forKey: key) else {
return DecodableDefault(wrappedValue: T.defaultDecodableValue)
}
return value
}
}
extension Array: DecodableDefaultValue where Element: Decodable {
public static var defaultDecodableValue: [Element] {
return []
}
}
extension Dictionary: DecodableDefaultValue where Key: Decodable, Value: Decodable {
public static var defaultDecodableValue: [Key: Value] {
return [:]
}
}
extension String: DecodableDefaultValue {
public static let defaultDecodableValue: String = ""
}
extension Int: DecodableDefaultValue {
public static let defaultDecodableValue: Int = 0
}
列举几个问题:
- 你不能select默认值(可以做不同的,但它更复杂)
- 如果你想使用
let
,你需要一个单独的不可变包装器。
假设我有以下可解码结构作为示例来说明我正在尝试做的事情:
struct Object: Decodable {
var id: String
var name: String
}
和这个 JSON:
[
{
"id": "a",
"name": "test"
},
{
"id": "b",
"name": null
}
]
请注意,名称 属性 有时可以是 null
。由于 json 键与结构 属性 名称匹配,因此这在很大程度上可以正常工作,因此我不需要 CodingKey
枚举,但是 name
属性 有时可以为空。但是,我不想让 name
可选,而是想替换默认字符串,所以我需要一个自定义初始化程序:
struct Object: Decodable {
var id: String
var name: String
init(from decoder: Decoder) throws {
...
self.name = <value from decoder> ?? "default name"
...
}
}
但这需要一个CodingKey
对象。我正在使用默认键。我现在还需要 CodingKey
枚举吗?即使我所有的键都匹配?或者有没有一种方法可以让自定义 Decodable
初始化程序仅使用键原样?
是否有某种我可以使用的默认容器?
let container = try decoder.container(keyedBy: <defaultContainer???>)
我尝试使用这两种变体,但都不起作用:
let container = try decoder.singleValueContainer()
let container = try decoder.unkeyedContainer()
我怎样才能拥有自定义 Decodable
初始化器 同时 使用默认键?
正如对您的问题所做的评论所说,编译器会为您生成一个 CodingKeys
对象。当键与您的枚举名称不匹配时,您可以实施自定义 enum
或 class 尊重您从源接收的 JSON 数据。
您可以这样实现您的对象:
struct TestObject: Codable {
var id: String
var name: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let nameOrNil = try? container.decode(String.self, forKey: .name)
self.id = id
self.name = nameOrNil ?? "Default value"
}
}
container
的 decode(_:forKey:)
方法可能会抛出错误,但如果您使实现解除错误,而不是使用 try?
返回一个可选值,您可以应用 nil只要名称未包含在 JSON.
这里证明:
let json = """
[
{
"id": "a",
"name": "test"
},
{
"id": "b",
"name": null
}
]
""".data(using: .utf8)!
let decodedArray = try JSONDecoder().decode([TestObject].self, from: json)
print(decodedArray)
参考文献:
CodingKeys
的自动生成真的很奇怪。它的范围和可用性根据您拥有的成员而变化。
假设您只有 Decodable
。这些编译:
struct Decodable: Swift.Decodable {
static var codingKeysType: CodingKeys.Type { CodingKeys.self }
}
struct Decodable: Swift.Decodable {
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}
...你可以把它们放在一起,如果你加上 private
.
struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}
…但是让 func
成为一个初始值设定项,同样,没有编译。
struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}
您可以将其更改为完全 Codable
,而不仅仅是 Decodable
…
struct Decodable: Codable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}
但是你不能在类型范围内使用 CodingKeys
,所以 属性 不会编译。
考虑到您可能不需要这样的 属性,只需使用 Codable
,向 Apple 提交错误并引用此答案,希望我们都可以切换到 Decodable
他们修好了。
问题是 CodingKeys
只有在没有完全手动符合相关协议的情况下才会自动为您生成。 (这对于 Objective-C 开发人员来说非常熟悉,如果您手动实现所有相关的访问器方法,属性 的支持 ivar 将不会自动合成。)
因此,在以下情况下,CodingKeys
不会自动为您创建:
您仅采用
Decodable
并实现了您自己的init(from:)
;您仅采用
Encodable
并实现了您自己的encode(to:)
;或您同时采用了
Encodable
和Decodable
(或者只是Codable
)并实现了您自己的init(from:)
和encode(to:)
。
所以你的情况属于上面的第一种情况。
有人建议您可以通过采用 Codable
来解决您的难题,即使您只实施了 init(from:)
并且可能不打算使用 Encodable
行为。实际上,这取决于您不打算真正使用的协议的副作用。
这并没有太大关系,采用 Codable
是可行的,但继续定义 CodingKeys
可能被认为“更正确”,而不是依赖于未实现的 Encodable
副作用:
struct Object: Decodable {
var id: String
var name: String
enum CodingKeys: String, CodingKey {
case id, name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = (try? container.decode(String.self, forKey: .name)) ?? "Default Value"
}
}
您可以使用 属性 包装器模拟该行为,但这不是完美的解决方案,而且有点老套。该问题已在 Swift 论坛上多次讨论。
使用 属性 包装器:
struct Object: Decodable {
var id: String
@DecodableDefault var name: String
}
属性 包装器的代码:
public protocol DecodableDefaultValue: Decodable {
static var defaultDecodableValue: Self { get }
}
@propertyWrapper
public struct DecodableDefault<T: Decodable>: Decodable {
public var wrappedValue: T
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(T.self)
}
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
}
extension DecodableDefault: Encodable where T: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
extension DecodableDefault: Equatable where T: Equatable { }
extension DecodableDefault: Hashable where T: Hashable { }
public extension KeyedDecodingContainer {
func decode<T: DecodableDefaultValue>(_: DecodableDefault<T>.Type, forKey key: Key) throws -> DecodableDefault<T> {
guard let value = try decodeIfPresent(DecodableDefault<T>.self, forKey: key) else {
return DecodableDefault(wrappedValue: T.defaultDecodableValue)
}
return value
}
}
extension Array: DecodableDefaultValue where Element: Decodable {
public static var defaultDecodableValue: [Element] {
return []
}
}
extension Dictionary: DecodableDefaultValue where Key: Decodable, Value: Decodable {
public static var defaultDecodableValue: [Key: Value] {
return [:]
}
}
extension String: DecodableDefaultValue {
public static let defaultDecodableValue: String = ""
}
extension Int: DecodableDefaultValue {
public static let defaultDecodableValue: Int = 0
}
列举几个问题:
- 你不能select默认值(可以做不同的,但它更复杂)
- 如果你想使用
let
,你需要一个单独的不可变包装器。