Decoding a JSON Field that Could Be String or Double with Swift

Luca Angeletti
The Startup
Published in
3 min readMay 1, 2020

Codable provides an elegant and declarative way of decoding a JSON in Swift. We just need to define a Type that matches the structure of the JSON.

Unfortunately, we don’t live in a perfect world so as an iOS Developer, you could need to decode a JSON with a field that doesn’t always have the same type.

Something like this

{
"movies": [
{
"title": "Start Trek: Generations",
"vote": 6.6
},
{
"title": "Start Trek: First Contact",
"vote": "7.6"
}
]
}

As you can see the vote field sometimes contains a Double

"vote": 6.6

and sometimes a String.

"vote": "7.6"

This is an error from who is providing the JSON but sometimes you just need to deal with it. So here’s a quick solution to solve this problem using Codable.

The Movies struct

First of all, let’s define a Swift struct to match the JSON structure.

struct Movies: Decodable {    let movies: [Movie]

struct Movie: Decodable {
let title: String
let vote: 🤔🤔🤔
}
}

Good, now we need to figure out which type to use for the vote field!

Since we know that it could contain a String or a Double we can define an Enum for that.

The StringOrDouble Enum

Here’s the core idea of this article. This enum is a Decodable Type that can match a string or a double found in the JSON.

enum StringOrDouble: Decodable {

case string(String)
case double(Double)

init(from decoder: Decoder) throws {
if let double = try? decoder.singleValueContainer().decode(Double.self) {
self = .double(double)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw Error.couldNotFindStringOrDouble
}
enum Error: Swift.Error {
case couldNotFindStringOrDouble
}
}

How does it work?

The Enum defines 2 cases each one with an associated value.

The string case will contain a String value. And the double case will contain… well a Double.

case string(String)
case double(Double)

Next, we provide a custom init which reads the value from the JSON and try to convert it first into a Double.

If the conversion succeeds then we get a value of type StringOrDouble.double where the associated value if the number found in the JSON.

If the conversion fails then we try to read a String to produce a value of type StringOrDouble.string.

If also the second conversion fails then an error is thrown.

Back to the Movies model

Now we can complete the implementation of the Movies model

struct Movies: Decodable {    let movies: [Movie]

struct Movie: Decodable {
let title: String
let vote: StringOrDouble
}
}

Testing

Finally, we can decode our JSON.

let data = """
{
"movies": [
{
"title": "Start Trek: Generations",
"vote": 6.6
},
{
"title": "Start Trek: First Contact",
"vote": "7.6"
}
]
}
""".data(using: .utf8)!
do {
let movies = try JSONDecoder().decode(Movies.self, from: data)
print(movies)
} catch {
print(error)
}

Result

Movies(movies: [
Movies.Movie(title: "Start Trek: Generations",
vote: StringOrDouble.double(6.6)),
Movies.Movie(title: "Start Trek: First Contact",
vote: StringOrDouble.string("7.6"))
])

--

--