1

How to decode an array of different JSON objects, where the same property on each object tells you what type to use to decode it:

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

Here is some code based on this similar answer which gets most of the way there, but fails because it doesn't know what CodingKeys are for .data:

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

Printed error is 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))

Thank you

0

1 Answer 1

5

You don't need the data coding key, since there's no "data" JSON key. Just decode the data property from the same decoder, based on the value of the JSON field:

struct SchemaObj: Decodable
{

    var dataType: String
    var data: MyData

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

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

If you plan to add more types to that list, then the if/else if can can become hard to manage, to improve the situation you can use a lookup table to address this:

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)
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.