【问题标题】:Swift Decode JSON with mixed types and possible sub structures使用混合类型和可能的子结构快速解码 JSON
【发布时间】:2019-01-16 14:17:44
【问题描述】:

我必须解码来自 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?

【问题讨论】:

    标签: json swift swift4 decodable


    【解决方案1】:

    使用这个结构

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

    希望有帮助

    【讨论】:

    • 谢谢,这确实有效,至少在测试集上,我现在将在完整数据集上尝试。
    【解决方案2】:

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

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

    【讨论】:

    • 感谢您的回复!这样做我有一个 EXC_BAD_ACCESS 就行了:if let item = try? container.decode(MenuCategory.self) {
    • 我无法使用您提供的 JSON 在情节提要中重现该内容。您需要深入挖掘堆栈跟踪。像这样的崩溃通常表明代码中其他地方可能存在不相关的内存管理错误。
    • 在玩了一些 do / catch 之后,我会收到以下错误:error : keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "menuEntryGroups", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "menuEntry", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").", underlyingError: nil))
    • 看起来您在menuEntryGroups/Index 0/menuEntry/Index 0 处缺少name 字段(与您在上面发布的JSON 不匹配)。无论如何,它都能准确地告诉你问题出在哪里。
    • 在仔细查看错误并与 json 进行比较之后,似乎 Group 和 Item 类型之间没有变化,因此他试图在具有“产品名称”。我将进一步调查为什么两者之间没有变化。谢谢
    猜你喜欢
    • 1970-01-01
    • 2020-10-01
    • 2022-01-11
    • 1970-01-01
    • 2011-06-19
    • 1970-01-01
    • 2017-08-12
    • 1970-01-01
    • 2020-07-01
    相关资源
    最近更新 更多