Pat’s Tech Weblog


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 {
case let .one(author):
	print("The author is named \(author)")
case let .many(authors):
	print("The authors are named \(authors.joined(separator: ", "))")
	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.