使用Decoder swift嵌套JSON

时间:2018-02-09 09:50:39

标签: json swift

我想从服务器解析JSON get但我有错误我不知道为什么?!! 这是我的结构:

struct MyResponse:Decodable {
    let cats: [Cats]
}

struct Cats: Decodable {
    let id: Int
    let name: String
    let menu: [Cats]
    enum CodingKeys:String, CodingKey {
        case name
        case id
        case menu = "SubMenu"
    }
}

并创建此扩展程序:

extension MyResponse.Cats {
    init(from decoder: Decoder) throws {
        let valus = try decoder.container(keyedBy: CodingKeys.self)
        name = try valus.decode(String.self, forKey: .name)
        id = try valus.decode(Int.self, forKey: .id)
        menu = try valus.decodeIfPresent(String.self, forKey: .menu)
    } 
}

我不知道如何解析这个json。这个json非常重要,因为这是商店的类别。这是我的json值:

{
"cats": [
    {
        "id": 15,
        "name": "کسب و کار ها",
        "menu": [
            {
                "id": 16,
                "name": "فروشگاهی",
                "menu": [
                    {
                        "id": 17,
                        "name": "ورزشی"
                    },
                    {
                        "id": 18,
                        "name": "نوشت افزار"
                    }
                ]
            },
            {
                "id": 19,
                "name": "خدماتی",
                "menu": ""
            }
        ]
    },

也许在将来的菜单中现在没有子菜单 如果菜单是零或有一些数据怎么处理??

编辑:和init中的这一行:

menu = try valus.decodeIfPresent(String.self, forKey: .menu)

有这个错误:

  

无法指定类型'字符串的值?'输入' [MyResponse.Cats]'

3 个答案:

答案 0 :(得分:1)

menu的值可以是

  • Cat
  • 的数组
  • 空字符串
  • 缺少钥匙

因此,您需要编写一个处理案例的自定义初始化程序。最简单的方法是解码Cat数组。如果失败则分配一个空数组。 此外,您需要一个伞形结构用于根对象。

struct Root: Decodable {
     let cats : [Cat] // it's recommended to name structs in singular form.
}

struct Cat : Decodable {
    let id : Int
    let name : String
    let menu : [Cat]

    enum CodingKeys : String, CodingKey { case name, id, menu }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        id = try values.decode(Int.self, forKey: .id)
        do {
            menu = try values.decode([Cat].self, forKey: .menu)
        } catch {
            menu = [Cat]()
        }
    }
}

或者声明菜单可选

let menu : [Cat]?

如果值不是nil

,请指定[Cat]
... } catch { menu = nil }

decodeIfPresent不起作用,因为该值可以是两种不同的类型。

答案 1 :(得分:0)

只需使[Cats]?结构符合//: Playground - noun: a place where people can play import Foundation struct MyResponse: Codable { let cats: [Cats] } struct Cats: Codable { let id: Int let name: String let menu: [Cats]? } // create json mock by encoding let cats3 = Cats(id: 3, name: "3", menu: nil) let cats2 = Cats(id: 2, name: "2", menu: nil) let cats1 = Cats(id: 1, name: "1", menu: [cats2, cats3]) let myResponse = MyResponse(cats: [cats1]) let json = try! JSONEncoder().encode(myResponse) print(String(bytes: json, encoding: String.Encoding.utf8)) // Optional("{\"cats\":[{\"id\":1,\"name\":\"1\",\"menu\":[{\"id\":2,\"name\":\"2\"},{\"id\":3,\"name\":\"3\"}]}]}") // create category data by decoding json (your actual question) do { let myResponseAgain = try JSONDecoder().decode(MyResponse.self, from: json) for cats in myResponseAgain.cats { print(cats.id) // 1 print(cats.name) // 1 print(cats.menu) // Optional([__lldb_expr_30.Cats(id: 2, name: "2", menu: nil), __lldb_expr_30.Cats(id: 3, name: "3", menu: nil)]) print(cats.menu![0].id) // 2 print(cats.menu![0].name) // 2 print(cats.menu![0].menu) // nil print(cats.menu![1].id) // 3 print(cats.menu![1].name) // 3 print(cats.menu![1].menu) // nil } } catch { print("something went wrong") } 协议并添加可选数组ValueType="DateTime"

FormatString="d"

答案 2 :(得分:0)

JSON示例包含一个表示"menu": ""的条目,而您的结构假定它是另一个Menu实例,null或完全不存在。

正如vadian指出的那样,如果你的子菜单有时会以""的形式返回,你可以编写一个自定义init(from:)方法来手动解析JSON,如图所示。但更好的方法是修复生成JSON的任何内容,以便"menu":""根本不存在,或者如果是,则为"menu":null(注意,没有引号)。最好在JSON中修复原始问题,而不是编写繁琐的JSON解析init(from:)来处理问题。

假设您修复了JSON,正如其他人所指出的那样,还存在另一个问题。您的Menu结构定义了一个名为submenu的属性,但您的JSON不使用该密钥。它使用menu。所以,你可以:

  1. 更改属性名称:

    struct Menu: Codable {
        let name: String
        let id: Int
        let menu: [Menu]?
    }
    

  2. 使用CodingKeys枚举:

    struct Menu: Codable {
        let name: String
        let id: Int
        let submenu: [Menu]?
    
        enum CodingKeys: String, CodingKey {
            case name, id
            case submenu = "menu"
        }
    }
    

  3. 将JSON更改为使用submenu键。

  4. 假设您修复了JSON,这表明您可以非常轻松地解析它。这使用方法2,如上所示:

    let data = """
        {
            "cats": [
                {
                    "id": 15,
                    "name": "کسب و کار ها",
                    "menu": [
                        {
                            "id": 16,
                            "name": "فروشگاهی",
                            "menu": [
                                {
                                    "id": 17,
                                    "name": "ورزشی"
                                },
                                {
                                    "id": 18,
                                    "name": "نوشت افزار"
                                }
                            ]
                        },
                        {
                            "id": 19,
                            "name": "خدماتی"
                        }
                    ]
                }
            ]
        }
        """.data(using: .utf8)!
    
    struct Respons: Codable {
        let cats: [Menu]
    }
    
    struct Menu: Codable {
        let name: String
        let id: Int
        let submenu: [Menu]?
    
        enum CodingKeys: String, CodingKey {
            case name, id
            case submenu = "menu"
        }
    }
    
    do {
        let object = try JSONDecoder().decode(Respons.self, from: data)
        print(object)
    } catch {
        print(error)
    }