Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make Enum (with associated type) Codable, successfully

Tags:

swift

codable

I'm having trouble getting my Codable type to properly decode. I have read a few tutorials on making Codable enums with associated types. I've searched for typos, name mismatches, or whatever but I can't spot anything wrong with this. And yet whenever I try to decode on of these structs (a Layer.. this defines an "Attribute" on a layer in an extension.. but all the other predefined parts of Layer are being properly en/de-coded), I hit a "key not found" exception in the decode(from decoder:) method.

extension Layer {

    struct Attribute: Codable {

        enum Value: Codable {

            case pulse(Double)
            case flash(Double)
            case draw(Double)

            private enum CodingKeys: String, CodingKey {
                case pulse, flash, draw
            }

            func encode(to encoder: Encoder) throws {

                var container = encoder.container(keyedBy: CodingKeys.self)

                switch self {
                case .pulse(let value):
                    try container.encode(value, forKey: .pulse)
                case .flash(let value):
                    try container.encode(value, forKey: .flash)
                case .draw(let value):
                    try container.encode(value, forKey: .draw)
                }
            }

            init(from decoder: Decoder) throws {

                let values = try decoder.container(keyedBy: CodingKeys.self)

                do {
                    let value = try values.decode(Double.self, forKey: .pulse)
                    self = .pulse(value)
                } catch (let error) {
                    print(error)
                }

                do {
                    let value = try values.decode(Double.self, forKey: .draw)
                    self = .draw(value)
                } catch (let error) {
                    print(error)
                }

                do {
                    let value = try values.decode(Double.self, forKey: .flash)
                    self = .flash(value)
                } catch (let error) {
                    print(error)
                }

                self = .draw(0.0)
            }


        }

        var value: Value

        init(value: Value) {
            self.value = value
        }
    }
}
like image 617
Alex Bollbach Avatar asked Dec 13 '17 06:12

Alex Bollbach


People also ask

How do you make an enum conform to Codable?

We can make enum with raw values conform to the Codable protocol by just adopting it. 1 We can make enum with raw value support Codable by just adopting it. By declaring protocol conformance, Role becomes a codable type. 2 We can use it in a codable context without doing any extra work.

What is Codable and Codable in Swift?

Codable; the data-parsing dream come true!Codable is the combined protocol of Swift's Decodable and Encodable protocols. Together they provide standard methods of decoding data for custom types and encoding data to be saved or transferred.

How do you make Codable?

The simplest way to make a type codable is to declare its properties using types that are already Codable . These types include standard library types like String , Int , and Double ; and Foundation types like Date , Data , and URL .

Can enum be Decodable?

Swift makes it is effortless to decode Enums from its raw value. All you need to do are make sure your enum is raw representable enum, e.g., can be represented by Int or String , and that your enum conforms to Codable protocol. Here is a struct and enum ready for encoding/decoding.


1 Answers

I think you should first check whether a key exists in the decoder's container or not, before decoding it. Currently, you are doing this:

do {
    let value = try values.decode(Double.self, forKey: .pulse)
    self = .pulse(value)
} catch (let error) {
    print(error)
}

do {
    let value = try values.decode(Double.self, forKey: .draw)
    self = .draw(value)
} catch (let error) {
    print(error)
}

do {
    let value = try values.decode(Double.self, forKey: .flash)
    self = .flash(value)
} catch (let error) {
    print(error)
}

There is no way that the decoder container is going to have all three keys in there, isn't it?

So, check before decoding:

if values.contains(.pulse) {
    do {
        let value = try values.decode(Double.self, forKey: .pulse)
        self = .pulse(value)
        return // remember to return here, so you don't set self back to .draw(0.0) again!
    } catch (let error) {
        print(error)
    }
} else if values.contains(.draw) {
    do {
        let value = try values.decode(Double.self, forKey: .draw)
        self = .draw(value)
        return
    } catch (let error) {
        print(error)
    }
} else if values.contains(.flash) {
    do {
        let value = try values.decode(Double.self, forKey: .flash)
        self = .flash(value)
        return
    } catch (let error) {
        print(error)
    }
}
like image 66
Sweeper Avatar answered Sep 21 '22 03:09

Sweeper