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
}
}
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.
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.
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.
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.
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
.
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.
We can now define the general element of our JSON
struct Element:Decodable {
let magicField: QuantumValue
}
That's it. Let's finally decode the JSON.
if let elms = try? JSONDecoder().decode([Element].self, from: data) {
print(elms)
}
[
Element(magicField: QuantumValue.string("one")),
Element(magicField: QuantumValue.float(1.0))
]
switch magicField {
case .string(let text):
println(text)
case .float(let num):
println(num)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With