SomeDecodable
Sometimes you want to parse JSON that doesn’t really agree with Codable’s synthesized init. Like if a key wants to be a single thing or an array of that thing. What am I talking about? Ok let’s say you want to parse some JSON into this:
struct BlogPost: Codable {
let author: String
}
So the JSON you get can look like this:
{
"author": "pat"
}
But TWIST, sometimes it gives you this:
{
"author": ["pat", "evil pat"]
}
With Codable
that usually means you have to write your own init(from decoder: any Decoder)
. And for me that means I usually have to google how to do that again.
Well now in this very specific case, I don’t anymore because I’m using this:
public enum SomeDecodable<T: Decodable>: Decodable {
case none, one(T), many([T])
public init(from decoder: any Decoder) throws {
let container = try! decoder.singleValueContainer()
if let one = try? container.decode(T.self) {
self = .one(one)
} else if let many = try? container.decode([T].self) {
self = .many(many)
} else {
self = .none
}
}
}
// SomeDecodable can have some Equatable, as a treat.
extension SomeDecodable: Equatable where T: Equatable { }
Now, in our model, we can use it:
struct BlogPost {
let author: SomeDecodable<String>
}
let post = try JSONDecoder().decode(BlogPost.self, from: json)
switch post.author {
case let .one(author):
print("The author is named \(author)")
case let .many(authors):
print("The authors are named \(authors.joined(separator: ", "))")
default:
print("I have no idea who the authors are")
}
With parameter pack iteration in Swift 6.0, we might be able to get rid of the enum and support more generic cases, but I haven’t figured out how to do it with Swift 5.10 yet.