Swift基于属性值类型解码对象的JSON数组

问题描述

如何解码不同 JSON 对象的数组,其中每个对象的相同属性告诉您使用什么类型来解码它:

let json =
"""
[
    {
        "@type": "FirstObject","number": 1
    },{
        "@type": "Secondobject","name": "myName"
    }
]
"""

这里有一些代码 based on this similar answer 可以实现大部分功能,但失败了,因为它不知道 .data 的 CodingKeys 是什么:

struct FirstObject: MyData {
    var dataType: String
    var number: Int
    
    enum CodingKeys: String,CodingKey {
        case dataType = "@type"
        case number
    }
}

struct Secondobject: MyData {
    var dataType: String
    var name: String
    
    enum CodingKeys: String,CodingKey {
        case dataType = "@type"
        case name
    }
}

struct SchemaObj: Decodable
{
    var dataType: String
    var data: MyData
    
    enum CodingKeys: String,CodingKey {
        case data
        case dataType = "@type"
    }
                
    enum ParseError: Error {
        case UnkNownSchemaType(Any)
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        dataType = try container.decode(String.self,forKey: .dataType)
        switch dataType {
        case "FirstObject":
            data = try container.decode(FirstObject.self,forKey: .data)
        case "Secondobject":
            data = try container.decode(Secondobject.self,forKey: .data)
        default:
            throw ParseError.UnkNownSchemaType(dataType)
        }
    }
}

do {
    let data = Data(json.utf8)
    let result = try JSONDecoder().decode([SchemaObj].self,from: data)
    print(result)
} catch {
    print(error)
}

打印错误keyNotFound(CodingKeys(stringValue: "data",intValue: nil),Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0",intValue: 0)],debugDescription: "No value associated with key CodingKeys(stringValue: \"data\",intValue: nil) (\"data\").",underlyingError: nil))

谢谢

解决方法

您不需要 data 编码密钥。只需根据 JSON 字段的值从同一解码器解码 data 属性:

enum CodingKeys: String,CodingKey {
    case dataType = "@type"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    dataType = try container.decode(String.self,forKey: .dataType)
    switch dataType {
    case "FirstObject":
        data = try FirstObject(from: decoder)
    case "SecondObject":
        data = try SecondObject(from: decoder)
    default:
        throw ParseError.UnknownSchemaType(dataType)
    }
}

如果您计划向该列表添加更多类型,那么 if/else if 可能会变得难以管理,您可以使用查找表来解决这个问题:

static let typeMapping: [String: MyData.Type] = [ "FirstObject": FirstObject.self,"SecondObject": SecondObject.self]

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let dataType = try container.decode(String.self,forKey: .dataType)
    
    guard let classToDecode = Self.typeMapping[dataType] else {
        throw ParseError.UnknownSchemaType(dataType)
    }
    
    self.dataType = dataType
    self.data = try classToDecode.init(from: decoder)
}