KeyedCodable 3.0.0

KeyedCodable 3.0.0

Dariusz Grzeszczak维护。



  • Dariusz Grzeszczak

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

默认情况下,点号用作分隔符,用于分隔内部键

@扁平类

平齐类功能允许您将属性分组到更小的部分。在下面的示例中,您可以查看以下几点:

  • 经度和纬度被放置在json的主类中,但在swift的模式中,它被移入了单独的结构Location
  • 经度和纬度都是可选的。如果其中任何一个缺失,位置属性将为nil。

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

您可能已经发现,如果数组的解码失败,解码任何数组元素的解码将失败。平齐数组 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][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 作为默认值

@Zero 功能在 JSON 中值没有设置(没有键或设置为 null)时为属性设置默认值 "zero"。如果在业务角度上看 0 和 nil 没有任何区别,你可以很容易地从你的 codable 中消除可选。

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

Transformers - @CodedBy, @EncodedBy, @DecodedBy

你是否曾经需要解码含多个日期格式的 JSON?或者你可能不得不在解码/编码过程中从一种值转换为另一种值并编写大量样板代码进行手动编码?Transformers 是为了使自定义转换更加容易而设计的!

要添加您的自定义转换器,您必须实现 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
    }
}

不可编解码格式化器示例

您甚至可以为不可编解码模型实现转换器,并像那样在您的可编解码树中使用它

示例 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,并且在标准 JSONEncoder/JSONDecoder 之外使用 KeyedJSONEncoder/KeyedJSONDecoder。请注意,Keyed 编码器继承自标准等效的版本,因此它们与 Apple 版本完全兼容。

可编码扩展

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)

它将抛出一个不正确的格式错误。键控版本将对这些进行成功解析。

键控制器 <> 包装器

您也可以使用标准 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 兼容,但您必须使用 swift 5.1 来使用与 @PropertyWrapper 相关的所有新功能。

迁移到 2.x.x 版本

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

  • 使用 KeyedJSONEncoder \ KeyedJSONDecoder 而不是 JSONEncoder \ JSONDecoder !!
  • 将您的 CodingKeys 改为 KeyedKey,并将您的映射移动到这里
  • 删除 KeyedCodable 协议
  • 删除构造函数和映射方法