Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I handle decoding two possible types for one key in Swift? [duplicate]

Tags:

swift

codable

I have a Codable struct that is used to decode incoming JSON. Unfortunately, sometimes one of its key's value is a string, and sometimes it is a float. I was able to cobble a couple of do/try/catch blocks below to get it to work, but is there a better way to handle this?

struct Project: Codable {
    public let version: Float

    init(from decoder: Decoder) throws {
        var decodedVersion: Float = 1.0
        do {
            decodedVersion = try values.decode(Float.self, forKey: .version)
        } catch {
            do {
                if let inVersion = try Float(values.decode(String.self, forKey: .version)) {
                    decodedVersion = inVersion
                }
            } catch {
                throw error
            }
        }
        version = decodedVersion
    }
}
like image 724
rawbee Avatar asked Nov 15 '17 22:11

rawbee


People also ask

How can I map coding keys in Swift?

In Swift 4.1, you can take advantage of the custom key encoding/decoding strategies on JSONEncoder and JSONDecoder, allowing you to provide a custom function to map coding keys.

What does codingkeys mean?

You can define custom coding keys to supply coding names for your properties. You do this by adding a special enum to your type. Open Custom coding keys and add this code inside the Employee type: CodingKeys is the special enum mentioned above.

How to convert coding keys from CamelCase to snake_case in Swift?

In Swift 4.1, if you rename your zip property to zipCode, you can take advantage of the key encoding/decoding strategies on JSONEncoder and JSONDecoder in order to automatically convert coding keys between camelCase and snake_case.

How to create custom coding keys for your properties?

You can define custom coding keys to supply coding names for your properties. You do this by adding a special enum to your type. Open Custom coding keys and add this code inside the Employee type: CodingKeys is the special enum mentioned above. It conforms to CodingKey and has String raw values. Hereโ€™s where you map favoriteToy to gift.


1 Answers

If in your JSON the value associated to a key can be sometimes a Float and sometimes a String (besides fixing this error on the backend ๐Ÿ˜‰) you could follow this approach.

Let's say this is your "funny" JSON

let data = """
[
{
    "magicField": "one"
},
{
    "magicField":1
}
]
""".data(using: .utf8)!

Good, how can we elegantly represent this kind of data in Swift?

struct Element:Decodable {
    let magicField: ???
}

We want magicField to always have a value, sometimes a Float and sometimes a String.

We can solve this with Quantum Mechanics... or with an Enum ๐Ÿ˜…

Let's define this type

enum QuantumValue: Decodable {

    case float(Float), string(String)

    init(from decoder: Decoder) throws {
        if let float = try? decoder.singleValueContainer().decode(Float.self) {
            self = .float(float)
            return
        }

        if let string = try? decoder.singleValueContainer().decode(String.self) {
            self = .string(string)
            return
        }

        throw QuantumError.missingValue
    }

    enum QuantumError:Error {
        case missingValue
    }
}

As you can see a value of type QuantumValue can hold a Float or a String. Always 1 and exactly 1 value.

Element

We can now define the general element of our JSON

struct Element:Decodable {
    let magicField: QuantumValue
}

Decoding

That's it. Let's finally decode the JSON.

if let elms = try? JSONDecoder().decode([Element].self, from: data) {
    print(elms)
}

Result

[
Element(magicField: QuantumValue.string("one")),
Element(magicField: QuantumValue.float(1.0))
]

Update (to answer Robโ€™s comment)

switch magicField {
    case .string(let text):
        println(text)
    case .float(let num):
        println(num)
}
like image 160
Luca Angeletti Avatar answered Nov 14 '22 23:11

Luca Angeletti