首页 > 解决方案 > 使用混合类型和可能的子结构快速解码 JSON

问题描述

我必须解码来自 Swift 4 中的 API 的给定 JSON 结构。问题是在树的某个点上,我在同一级别有不同类型的数据,其中一种类型可以有子元素。

我已经尝试了几种 JSONDecoder 和 Decodable 技术,但到目前为止我还没有找到解决方案。

简化的 JSON:

{
    "menuName": "Menu 1",
    "menuId": 1,
    "menuGroups": [
        {
            "type": "group",
            "name": "Group 1",
            "menuEntry": [
                {
                    "type": "group",
                    "name": "Sub Group 1.1",
                    "menuEntry": [
                        {
                            "type": "menuItem",
                            "productName": "Item 1",
                            "productPrice": "9.00"
                        },
                        {
                            "type": "menuItem",
                            "productName": "Item 2",
                            "productPrice": "12.00"
                        }
                    ]
                }, {
                    "type": "menuItem",
                    "productName": "Item 3",
                    "productPrice": "9.00"
                }
            ]
        }
    ]
}

这是我正在尝试使用的解码器:

struct Menu: Decodable {
    let menuName: String
    let menuId: Int
    let categories: [MenuCategory]

    enum CodingKeys : String, CodingKey {
        case menuName
        case menuId
        case categories = "menuGroups"
    }
}

struct MenuCategory: Decodable {
    let type: String
    let name: String
    let items: [CategoryItem]

    enum CodingKeys : String, CodingKey {
        case type
        case name
        case items = "menuEntry"
    }


}

enum CategoryItem: Decodable {
    case group(MenuCategory)
    case menuItem(MenuItem)

    public init(from decoder: Decoder) throws {

        let container = try decoder.singleValueContainer()
        do {
            let item = try container.decode(MenuCategory.self)
            self = .group(item)
            return
        } catch let err {
            print("error decoding category: \(err)")
        }

        do {
            let item = try container.decode(MenuItem.self)
            self = .menuItem(item)
            return
        } catch let err {
            print("error decoding item: \(err)")
        }
        try self.init(from: decoder)
    }
}

struct MenuItem: Decodable {
    let type: String
    let productName: String
    let productPrice: String

    enum CodingKeys : String, CodingKey {
        case type = "type"
        case productName
        case productPrice
    }
}

我认为使用:

let container = try decoder.singleValueContainer()

是错误的,因为容器不应该是单值容器,但我不知道从这里选择/做什么......

有人对此有想法吗?您将如何解码示例中的一些 JSON?

标签: jsonswiftswift4decodable

解决方案


您非常非常接近,并且在设计数据结构方面做得很好。您只需要尝试解码CategoryItem.

public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    if let item = try? container.decode(MenuCategory.self) {
        self = .group(item)
    } else if let item = try? container.decode(MenuItem.self) {
        self = .menuItem(item)
    } else {
        throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath,
                                                debugDescription: "Not a group or item"))
    }
}

这个容器是一个单值容器,因为在解码的这一点上,你只解码一个东西,一个组或一个项目。由这些单个值中的每一个来处理它们的子组件。


推荐阅读