单个 JSON 返回中的多个可编码对象类型

Posted

技术标签:

【中文标题】单个 JSON 返回中的多个可编码对象类型【英文标题】:Multiple codable object types out of a single JSON return 【发布时间】:2018-01-22 19:33:42 【问题描述】:

我的困境是我在 JSON 响应中从一个表中接收到两种不同的对象类型。这是返回中两种类型的响应的示例。

"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
    
]

我们希望在这里创建两种不同类型的对象。一个textOnlyItem 和一个imageWithTextItem

有没有一种方法可以创建一个子类或扩展,可以从 Bool 中删除,由 imageName 是否为 null 定义?

感谢大家的帮助。

【问题讨论】:

【参考方案1】:

您不需要两个不同的对象。只需将imageNametextItems 声明为可选,这将处理null 的情况。

您可以简单地检查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) 

【讨论】:

问题是我确实需要多个对象。在项目中,每个实例都有大量的实例(以前他们使用硬编码的数据源,并且有两个对象。现在两个对象都有一个返回)所以我们需要提取每个对象类型,或者重组大量他们的应用程序。如果无法在此级别将它们分开,这是有可能的。 那你得写一个自定义的初始化器。【参考方案2】:

我实际上喜欢 vadian 的一种方法。但我认为这需要对您的情况进行重大重构。

另一种方法是只使用JSONSerialization 并手动构建您的异构数组。 JSONSerialization 没有被弃用,它只是不像 JSONDecoder 那样自动执行。

另一种方法是使用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
    

然后:

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)

我不相信这比仅使用 JSONSerialization 更好或更差,但这是另一种方法。

【讨论】:

嗨,Rob,感谢您的回复。经过一些研究,我认为我希望的确切功能在技术上是不可能的。

以上是关于单个 JSON 返回中的多个可编码对象类型的主要内容,如果未能解决你的问题,请参考以下文章

Swift 4 可编码;如何使用单个根级密钥解码对象

mySQL中的JSON操作

Swift:如何将可编码类型作为函数输入传递给编码或解码 json

如何将单个 .NET 类型映射到 ElasticSearch/NEST 中的多个嵌套对象类型?

可编码的自定义类类型属性不能从 JSON 初始化它自己的属性

将对象转换为可编码对象失败