Swift 如何给常量 Decodable 对象动态添加属性?

How to dynamically add properties to a constant Decodable object in Swift?

背景

基本上我 api returns 是这样的:

    "order_detail": [
        {
            "id": 6938,
            "order_id": 6404,
            "item_price": "4",
            ..
            "item": {
                "id": 12644,
                "ref": "Iced Caffe Americano",
                "short_description": "",
                ..

在我的可解码对象中我得到了这个

public struct OrderDetail: Decodable {
    public let id: Int
    public let order_id: Int
    public let item_price: String?
    ..
    public let item: Item?

public struct Item: Decodable {
    public var id: Int
    public var ref: String?
    public var short_description: String?

问题是在代码的其他地方,有一个方法期望 Item 对象具有 item_price

问题

我想做的是调整或改变这个常量 Item 对象并动态添加 item_price 属性 到它。我该怎么做?

解决方法,其他解决方案

1。更改 json

我知道同样的问题还有许多其他解决方案(我正在研究它,这只是修改 api 端点以满足我的需要)。但是这个选项还是并非总是可能(即假设后端团队是独立的)

2。改变函数期望值

这也是可能的,但也不便宜,因为此功能用于应用程序中我可能无法控制的许多其他地方

如果要将 属性 添加到不属于其 JSON 表示的 Decodable 类型,那么只需要声明一个 CodingKey 一致类型,并且省略特定的 属性 名称,以便自动合成的 init(from decoder:Decoder) 初始化程序知道不要在 JSON.

中查找该值

顺便说一句,您还应该遵守 Swift 命名约定(变量名小驼峰命名)并使用 CodingKey 将 JSON 键映射到 属性 名称。

public struct Item: Decodable {
    public var id: Int
    public var ref: String?
    public var shortDescription: String?
    public var itemPrice: String? // or whatever else its type needs to be

    private enum CodingKeys: String, CodingKey {
        case id, ref, shortDescription = "short_description"
    }
}

这是实现此目的的一种方法

在OrderDetail解码中接管Item的初始化。

struct OrderDetail: Decodable {
    let id: Int
    let orderId: Int
    let itemPrice: String?
    let item: Item

    private enum OrderDetailCodingKey: CodingKey {
        case id
        case orderId
        case itemPrice
        case item
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: OrderDetailCodingKey.self)
        self.id = try container.decode(Int.self, forKey: .id)
        self.orderId = try container.decode(Int.self, forKey: .orderId)

        let itemPrice = try container.decode(String?.self, forKey: .itemPrice)
        self.itemPrice = itemPrice
        self.item = try Item(from: decoder, itemPrice: itemPrice)
    }
}

使用自定义初始化程序创建您的项目。

struct Item: Decodable {
    let id: Int
    let ref: String?
    let shortDescription: String?
    let itemPrice: String?

    private enum ItemCodingKeys: CodingKey {
        case id
        case ref
        case shortDescription
    }

    init(from decoder: Decoder, itemPrice: String?) throws {
        let container = try decoder.container(keyedBy: ItemCodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
        self.ref = try? container.decode(String.self, forKey: .ref)
        self.shortDescription = try? container.decode(String.self, forKey: .shortDescription)
        self.itemPrice = itemPrice
    }
}

您可以调用以下函数来测试功能:

private func test() {
        let json = """
            {"id":6938,"orderId":6404,"itemPrice":"4","item":{"id":12644,"ref":"Iced Caffe Americano","shortDescription":""}}
        """
        let data = json.data(using: .utf8)
        let decoder = JSONDecoder()
        if let data = data {
            do {
                let order = try decoder.decode(OrderDetail.self, from: data)
                print(order)
            } catch let jsonError {
                os_log("JSON decoding failed [%@]", String(describing: jsonError))
            }
        } else {
            os_log("No data found")
        }
    }