Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
DSL for brute force enum decoding
public struct DecodingRoutine<T: Decodable> {
let decode: (Decoder) throws -> T?
}
extension DecodingRoutine {
public static func `default`(_ value: T) -> DecodingRoutine {
return .init { _ in value }
}
}
extension DecodingRoutine {
public static func `case`<Case: Decodable>(_ function: @escaping (Case) -> T) -> DecodingRoutine {
return .init { decoder in
guard let value = try? Case(from: decoder) else { return nil }
return function(value)
}
}
}
extension DecodingRoutine {
public static func `case`<Key: Decodable & Equatable, Case: Decodable>(_ function: @escaping (Case) -> T, when key: String, equals value: Key) -> DecodingRoutine {
return .init { decoder in
guard
let keyContainer = try? decoder.container(keyedBy: AnyCodingKey.self),
let key = try? keyContainer.decode(Key.self, forKey: AnyCodingKey(key)),
key == value
else { return nil }
return try function(Case(from: decoder))
}
}
}
extension Decoder {
public func decodeFirst<T: Decodable>(from routines: [DecodingRoutine<T>]) throws -> T {
guard let value = try routines.lazy.compactMap({ try $0.decode(self) }).first else {
throw DecodingError.typeMismatch(T.self, .init(codingPath: codingPath, debugDescription: "None of the provided options were able to decode the data"))
}
return value
}
}
@IanKeen

This comment has been minimized.

Copy link
Owner Author

IanKeen commented Apr 18, 2019

@IanKeen

This comment has been minimized.

Copy link
Owner Author

IanKeen commented Apr 18, 2019

Example usage:

struct Foo: Codable { let value: String }

enum Multi {
    case foo(Foo)
    case bar(Int)
}

extension Multi: Decodable {
    init(from decoder: Decoder) throws {
        self = try decoder.decodeFirst(from: [ // these routines will be attempted in order
            .case(Multi.bar),
            .case(Multi.foo, when: "type", equals: "foo"),
            .default(.bar(100)) // used if all the above fail
        ])
    }
}

extension Multi: Encodable {
    func encode(to encoder: Encoder) throws {
        switch self {
        case .foo(let value):
            var container = encoder.container(keyedBy: AnyCodingKey.self)
            try container.encode("foo", forKey: "type")
            try value.encode(to: encoder)

        case .bar(let value):
            try value.encode(to: encoder)
        }
    }
}

let items: [Multi] = [.foo(.init(value: "hello world")), .bar(42)]
let data = try! JSONEncoder().encode(items)
print(String(data: data, encoding: .utf8)!) // [{"foo":"type","value":{"value":"hello world"}},42]
let decoded = try! JSONDecoder().decode([Multi].self, from: data)
print(decoded) // [Multi.foo(Foo(value: "hello world")), Multi.bar(42)]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.