Swift 使用混合类型和可能的子结构解码 JSON

Swift Decode JSON with mixed types and possible sub structures

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

我尝试了几种JSON解码器和可解码技术,但到目前为止我还没有找到解决方案。

简化 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?

您非常非常接近,并且在设计数据结构方面做得很好。您只需要尝试解码 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"))
    }
}

此容器是单值容器,因为在解码的这一点上,您只解码一个事物、一组或一个项目。这取决于每个单一值来处理它们的子组件。

使用这个结构

struct YourStruct: Codable {
    let menuName: String
    let menuID: Int
    let menuGroups: [MenuGroup]

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

struct MenuGroup: Codable {
    let type, name: String
    let menuEntry: [MenuGroupMenuEntry]
}

struct MenuGroupMenuEntry: Codable {
    let type: String
    let name: String?
    let menuEntry: [MenuEntryMenuEntry]?
    let productName, productPrice: String?
}

struct MenuEntryMenuEntry: Codable {
    let type, productName, productPrice: String
}

并在检查您没有错误后进入数据任务

if let data = data {
                let decoder = JSONDecoder()
                guard let decodedJson = try? decoder.decode(YourStruct.self, from: data) else { completion(nil) ; return }

            }

希望对您有所帮助