可编码性

SPM Git Version Build Status license

在 Swift 中使用 Codable 类型的实用助手

安装

Swift 包管理器

将以下内容添加到您的 Package.swift 依赖项中

.package(url: "https://github.com/yonaskolb/Codability.git", from: "0.2.0"),

然后在任何需要的地方导入:import Codability

助手

无效元素策略

默认情况下,如果数组或字典中的单个元素失败,Decodable 将抛出错误。InvalidElementStrategy 是一个枚举,可让您控制此行为。它有多种情况

解码数组或字典时,请使用 decodeArraydecodeDictionary 函数(也有 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 类型

使用 Codable 的缺点是您无法编码和解码类型混合或未知的属性,例如 [String: Any][Any]Any。这些在许多 API 中有时是必要的恶,而 AnyCodable 使支持这些类型变得容易。

有 2 种不同的使用方式

作为 Codable 属性

这样做的好处是您可以使用合成的 codable 函数。但缺点是这些值必须使用 AnyCodable.value 解包。您可以在对象上添加自定义 setter 和 getter,以使访问这些值更容易

struct AnyContainer: Codable {
    let dictionary: [String: AnyCodable]
    let array: [AnyCodable]
    let value: AnyCodable
}

自定义解码和编码函数

这使您可以保留正常的结构,但需要使用 decodeAnyencodeAny 函数。如果您出于其他原因必须实现自定义 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
    }
}

原始 CodingKey

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

泛型解码函数

KeyedDecodingContainerUnkeyedDecodingContainer 上的默认解码函数都要求显式传入类型。Codabilty 添加了泛型函数来消除这种需要,使您的 init(from:) 更加简洁。key 参数也变为未命名。

Codabality 提供的所有辅助函数(例如 decodeAnydecodeArraydecodeDictionary 函数)也具有这些泛型变体,包括 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")
    }
}

其他 Codability 助手

JohnSundell/Codextended

归属

感谢 @mattt 和 Flight-School/AnyCodableAnyCodable 支持奠定了基础。许可证