简体   繁体   English

单个JSON返回值中有多种可编码对象类型

[英]Multiple codable object types out of a single JSON return

My dilemma is I'm receiving two different object types from one table in a JSON response. 我的困境是我从JSON响应的一个表中接收了两种不同的对象类型。 Here is an example of the response of both types in a return. 这是返回中两种类型的响应的示例。

"supplementaryItems": [
    {
        "header": "Doodle",
        "subHeader": "It's a drawing.",
        "slideID": 4,
        "imageName": null,
        "textItems": null,
        "sortOrder": 0
    },
    {
        "header": "Cell Phones",
        "subHeader": "No phones please",
        "slideID": 8,
        "imageName": "welcome_icon_cellphones",
        "textItems": ["first","second","third"],
        "sortOrder": 1
    }
]

What we're hoping to do is create two different types of objects here. 我们希望做的是在这里创建两种不同类型的对象。 A textOnlyItem , and a imageWithTextItem . 一个textOnlyItem和一个imageWithTextItem

Is there a way to create one as a subclass or extension that can be keyed off of a Bool defined by whether imageName is null or not? 有没有一种方法可以将其创建为可以通过imageName是否为null定义的Bool禁用的子类或扩展?

Thanks for any help all. 谢谢大家的帮助。

You don't need two different objects. 您不需要两个不同的对象。 Just declare imageName and textItems as optional, this handles the null case. 只需将imageNametextItems声明为可选,即可处理null情况。

You can simply check whether imageName is nil 您可以简单地检查imageName是否为nil

let jsonString = """
{"supplementaryItems": [
{
"header": "Doodle",
"subHeader": "It's a drawing.",
"slideID": 4,
"imageName": null,
"textItems": null,
"sortOrder": 0
},
{
"header": "Cell Phones",
"subHeader": "No phones please",
"slideID": 8,
"imageName": "welcome_icon_cellphones",
"textItems": ["first","second","third"],
"sortOrder": 1
}
]
}
"""

struct Root : Decodable {
    let supplementaryItems : [SupplementaryItem]
}

struct SupplementaryItem : Decodable {
    let header : String
    let subHeader : String
    let slideID : Int
    let imageName : String?
    let textItems : [String]?
    let sortOrder : Int
}

do {
    let data = Data(jsonString.utf8)
    let result = try JSONDecoder().decode(Root.self, from: data)
    for item in result.supplementaryItems {
       if let imageName = item.imageName {
           print(imageName + " has text items")
       } else {
           print(item.header + " has no text items")
       }
    }
} catch { print(error) }

I actually like vadian's approach, of one type. 我实际上喜欢vadian的一种方法。 But I gather that would require significant refactoring in your situation. 但我认为这将需要对您的情况进行重大重构。

The other approach is to just use JSONSerialization and build your heterogeneous array manually. 另一种方法是仅使用JSONSerialization并手动构建异构数组。 JSONSerialization isn't deprecated, it just doesn't do it automatically like JSONDecoder . JSONSerialization并没有被弃用,只是没有像JSONDecoder这样自动JSONDecoder

Another approach is to use JSONDecoder , writing custom initializer that tries to decode it as ImageItem , and if that fails, try decoding it as TextItem : 另一种方法是使用JSONDecoder ,编写尝试将其解码为ImageItem自定义初始化程序,如果失败,请尝试将其解码为TextItem

protocol SupplementaryItem {
    var header:    String    { get }
    var subHeader: String    { get }
    var slideID:   Int       { get }
    var sortOrder: Int       { get }
    var textItems: [String]? { get }
}

struct TextItem: SupplementaryItem, Codable {
    let header:    String
    let subHeader: String
    let slideID:   Int
    let sortOrder: Int
    let textItems: [String]?
}

struct ImageItem: SupplementaryItem, Codable {
    let header:    String
    let subHeader: String
    let slideID:   Int
    let sortOrder: Int
    let textItems: [String]?
    let imageName: String
}

struct ResponseObject: Decodable {
    let supplementaryItems: [SupplementaryItem]

    enum CodingKeys: String, CodingKey {
        case supplementaryItems
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        var container = try values.nestedUnkeyedContainer(forKey: .supplementaryItems)
        if container.count == nil {
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expected array for supplementaryItems")
        }
        var items = [SupplementaryItem]()
        while !container.isAtEnd {
            if let item = try? container.decodeIfPresent(ImageItem.self), let imageItem = item {
                items.append(imageItem)
            } else {
                let textItem = try container.decode(TextItem.self)
                items.append(textItem)
            }
        }
        supplementaryItems = items
    }
}

Then: 然后:

let string = """
    {
        "supplementaryItems": [
            {
                "header": "Doodle",
                "subHeader": "It's a drawing.",
                "slideID": 4,
                "imageName": "foo",
                "textItems": null,
                "sortOrder": 0
            },
            {
                "header": "Cell Phones",
                "subHeader": "No phones please",
                "slideID": 8,
                "imageName": "welcome_icon_cellphones",
                "textItems": ["first","second","third"],
                "sortOrder": 1
            }
        ]
    }
    """

let data = string.data(using: .utf8)!

let json = try! JSONDecoder().decode(ResponseObject.self, from: data)
print(json)

I'm not convinced this is any better or worse than just using JSONSerialization , but it's another approach. 我不相信这比仅使用JSONSerialization更好或更糟,但这是另一种方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM