4

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

2
  • 1
    You should include some sample JSON in your question. Commented Sep 4, 2019 at 9:45
  • @DávidPásztor You're right, that made it a lot better, I've included a JSON. Commented Sep 4, 2019 at 9:50

1 Answer 1

7

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