:codable (karma)

A mobile engineer trying to figure out how to be a good father

Decode a single key against multiple data types

So picture this... One day your creating an application that takes in a JSON feed and displays that data on a screen. Sounds like a pretty standard day right?

Although when you look closer at the data you realise something is off...

{
    "data": [{
            "title": "content title",
            "value": 20
        },
        {
            "title": "Another body title",
            "value": "Body content that isn't lorem ipsum"
        }
    ]
}

It turns out that the value key has two different foundation types. You ask yourself, how are you going to decode this.

Today, we go through that answer.

Decoding Multiple Types

If the value field was a definite String our struct would look something like this:

struct dto: Decodable {
    let data: Data
    
    struct Data: Decodable {
        let title: String
        let value: String
    }
}

Obviously this won't quite cut it if value can something be a Int as well.

But with a small change we can get it working.

First we need to declare a Value enum (this is just named based on the data field, so feel free to call it whatever makes sense for your project).

enum Value: Decoable {
    case stringValue(String)
    case intValue(Int)
}

So using this Value as the data type in the original structure, along with an additional init method gives us the following

struct dto: Decodable {
    let data: Data
    
    struct Data: Decodable {
        let title: String
        let value: Value
        
        enum Value: Decoable {
            case stringValue(String)
            case intValue(Int)
            
            init(from decoder: Decoder) throws {
                let container = try decoder.singleValueContainer()
                
                if let stringValue = try? container.decode(String.self) {
                    self = .stringValue(stringValue)
                    return
                } else if let intValue = try? container.decode(Int.self) {
                    self = .intValue(intValue)
                    return
                }
                
                throw DecodingError.typeMismatch(
                    Value.self, 
                    DecodingError.Context(
                        codingPath: decoder.codingPath,
                        debugDescription: "Could not decode type for value"
                    )
                )
            }
        }
    }
}

and there we have it, a way to decode a multi-type value.

Tagged with: