Carthage compatible Version License Platform

目标

KeyedCodable 是对 Swift 的 Codable 的补充,专为自动嵌套键映射而设计。 它的目标是避免手动实现 Encodable/Decodable,并使编码/解码更容易、更具可读性、更少的样板代码,最重要的是,与“标准”Codable 完全兼容。

嵌套键

首先,请看 Apple 提供的 Codable 示例。

原生 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)
    }
}

KeyedCodable

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 类

Flat 类特性允许您将属性分组为更小的部分。 在下面的示例中,您可以看到

JSON 示例

{
    "longitude": 3.2,
    "latitude": 3.4,
    
    "greeting": "hallo"
}

KeyedCodable

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 数组

您可能发现,如果任何数组元素的解码失败,则解码数组将失败。 Flat 数组 KeyedCodable 的特性会忽略不正确的元素,并创建一个仅包含有效元素的数组。 在下面的示例中,即使第二个元素的解码失败,array 属性也将包含三个元素 [1,3,4]。

{
    "array": [
        {
        "element": 1
        },
        {},
        {
        "element": 3
        },
        {
        "element": 4
        }
    ]
}

KeyedCodable

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] - 默认情况下它是“空字符串 + 点”

KeyOptions

如果 json 的键和 KeyedCodable 使用的分隔符之间存在冲突,您可以使用 KeyOptions 来配置映射功能。 您可以通过在 CodingKeys 中提供 options: KeyOptions? 属性来实现(使用 nil 使用默认值)。 您也可以通过设置 .none 值来禁用该功能。

示例

JSON 示例

{
    "* 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
    }
    ]
}

KeyedCodable

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

@Zero 作为默认值

如果 JSON 中未设置值(根本没有键或设置了空值),则 @Zero 功能会为属性设置默认的“零”值。 如果您发现从业务角度来看 0 和 nil 之间没有区别,您可以轻松地从您的 codable 中删除 optional。

JSON 示例

{
    "name": "Jan"
}

KeyedCodable

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
}

转换器 - @CodedBy, @EncodedBy, @DecodedBy

您是否曾经需要解码 JSON 中的多个日期格式? 或者您是否必须在解码/编码过程中添加从一个值到另一个值的自定义转换,并且您必须编写大量的样板代码进行手动编码? 转换器旨在使自定义转换变得更容易!

要添加您的自定义转换器,您必须实现 DecodableTransformerEncodableTransformerTransformer 以进行双向转换。

日期格式化器示例

JSON 示例

{
    "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 模型实现转换器,并在您的 Codable 树中使用它,如下所示

JSON 示例

{
    "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 版本完全兼容。

Codable 扩展

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 编码器支持简单类型,例如 StringInt 等。 例如,当我们尝试使用标准 JSONDecoder 解码 Int

let number = try JSONDecoder().decode(Int.self, from: data)

它将抛出一个不正确的格式错误。 Keyed 版本将成功解析。

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

KeyedConfig

如前所述,可以在 KeyedKey 级别设置键选项(例如分隔符),也可以全局设置。

为此,您需要设置 KeyedConfig.default.keyOptions 的值。

除了键选项之外,还可以设置 Codable 扩展中默认使用的编码器。

迁移到 3.x.x 版本

3.0.0 版本与 2.x.x 版本向后兼容,但是,您必须对与 @PropertyWrapper 关联的所有新特性使用 swift 5.1

迁移到 2.x.x 版本

遗憾的是,2.x.x 版本与 1.x.x 版本不兼容,但我相信新方法更好,并且比以前的版本带来更少的样板代码。 无需添加任何手动映射实现,它非常简单,因此我强烈建议迁移到新版本。 您只需要