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"
    }
}

containerdecode(_: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 不会自动为您创建:

  1. 您仅采用 Decodable 并实现了您自己的 init(from:);

  2. 您仅采用 Encodable 并实现了您自己的 encode(to:);或

  3. 您同时采用了 EncodableDecodable(或者只是 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,你需要一个单独的不可变包装器。