1
votes

I have a Swift app which I am in the process of converting over to use the Codable protocol (instead of EVReflection, which changed so much that I could no longer make it work). When transacting with the app server, my code generates an object of class "ServerResponse", which contains a number of variables - one of which is "responseObject" - which can be any number of different objects from a user, to a message, or others. Because Codable doesn't use "decodeIfPresent" by default, I was getting errors during certain transactions, and had to override init(from decoder: Decoder) to prevent this. Now I am left with the challenge of figuring out how to either leave the raw JSON string intact for decoding into the correct object type later by the calling method, or some other similar fix. Bottom line: I need responseObject to be flexible and allow any type of object that my server chooses to send.

If anyone has any suggestions, I'd be grateful. I'll be happy to share code if that will help, but I didn't think it would be since this question is mostly conceptual in nature.

1
The general solution to attempting to decode something which may be one of several types is to attempt to decode it as one type, and if that fails due to a type mismatch, attempt the next type. Is responseObject like this, or is it a completely unconstrained type? Can you give a concrete example of what the JSON payload might look like?Itai Ferber
It's not COMPLETELY uncontrained, but there are 34 different kinds of objects at this point in my app that could be passed. What I'd really like is to leave the raw JSON completely untouched so I could decode it manually from the calling method. Is there a way to do that?Joel
Not at the moment, no. It's currently a feature being considered.Itai Ferber

1 Answers

3
votes

You can do something similar to this:-

struct CustomAttribute: Codable {
var attributeCode: String?
var intValue: Int?
var stringValue: String?
var stringArrayValue: [String]?

enum CodingKeys: String, CodingKey {

    case attributeCode = "attribute_code"
    case intValue = "value"
    case stringValue
    case stringArrayValue
}

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)

    attributeCode = try values.decode(String.self, forKey: .attributeCode)
    if let string = try? values.decode(String.self, forKey: .intValue) {
        stringValue = string
    } else if let int = try? values.decode(Int.self, forKey: .intValue) {
        intValue = int
    } else if let intArray = try? values.decode([String].self, forKey: .intValue) {
        stringArrayValue = intArray
    }
}

}

Here value can be of three types and I manually identify which type it is.