3

It's not about decoding a property value with multiple types (int, string)..

I have an object called data it can return multiple types, What could be done at this point may look something like this :

enum MyData: Codable {
    case ObjOne(groupObject)
    case ObjTwo(imageObject)

    init(from decoder: Decoder) throws {
        let value = try decoder.singleValueContainer()

        if let v = try? value.decode(groupObject.self) {
            self = .ObjOne(v)
            return
        } else if let v = try? value.decode(imageObject.self) {
            self = .ObjTwo(v)
            return
        }

        throw Rating.ParseError.notRecognizedType(value)
    }

    enum ParseError: Error {
        case notRecognizedType(Any)
    }
}

The issue here is that i try to make MyData decode the object based on another value that was used in the previous decoding process, in short words, i want to pass a value to MyData so it can determine which to decode

I have this

enum ContentType: String, Codable {
    case linear
    case grid
    case slider
}

And i want MyData to know about this ContentType value so MyData can determine how the flow will go,

So where did ContentType come from ? it's in the same list of properties in the previous main object, coming from something that looks like this

struct Catalog: Codable {
    var dataType: ContentType?
    var data: MyData? 
}

What i want to achieve in more simple words ?

struct Catalog: Codable {
    var dataType: ContentType?
    var data: MyData<dataType>? <--// i know this is not possible,
    // -- but i want MyData to know about the dataType value that will be decoded
}

--------- JSON i want to parse

[{
  "data_type": "group",
  "data": {
    "group_id": 127 // this refers to object : groupObject
  }
},
{
  "data_type": "image",
  "data": {
    "image": "http://google.com/favicon" // this is referring : imageObject
  }
}
]

You see the point above, is that "data" can return different objects, based on the value of data_type

Osa
  • 1,922
  • 7
  • 30
  • 51

1 Answers1

4

Rather than using generics I created an empty protocol that conforms to Decodable and used that as the type for data. Then the content structs needs to conform to this protocol.

protocol MyData: Decodable {}

struct Group: MyData {
    let groupId: Int
}

struct Image: MyData {
    let image: String
}

struct Catalog: Decodable {
    var dataType: String
    var data: MyData

    enum CodingKeys: String, CodingKey  {
        case dataType, data
    }

    enum ParseError: Error {
        case notRecognizedType(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 "group":
            data = try container.decode(Group.self, forKey: .data)
        case "image":
            data = try container.decode(Image.self, forKey: .data)
        default:
            throw ParseError.notRecognizedType(dataType)
        }
    }
}

Note that I didn't use the enum ContentType in the init because it didn't match the sample json data but that should be easily fixed.

Standard code for using this solution

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase

    let result = try decoder.decode([Catalog].self, from: data)
    print(result)
} catch {
    print(error)
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52