5

I have to deserialize a JSON string like this:

{ "name" : "John Smith", "value" : "someValue" }

in Swift 4, where "value" should be a enum and the whole object is a struct like:

struct MyType {
    name: String?
    value: Value?
}

At some point in the future, there might be new enum values added in the backend so I thought it would be smart to have some fallback.

I thought I could create a enum like

enum Value {
    case someValue
    case someOtherValue
    case unknown(value: String)
}

but I just can't wrap my head around how to deserialize that enum and make it work. Previously I simply used a String enum, but deserializing unknown values throws errors.

Is there a simple way to make that work or should I deserialize the value as a String and create a custom getter in the struct with a switch statement to return one of the cases (probably not even in the struct itself but in my view model)?

jscs
  • 63,694
  • 13
  • 151
  • 195
xxtesaxx
  • 6,175
  • 2
  • 31
  • 50
  • 1
    You can easily write a customized `init(coder:)` for either type. But if new enum values are getting added in your own backend without letting the client team(s) know, that's not a problem to solve technologically... – jscs Dec 04 '17 at 13:25
  • Its not about notifying but backwards compatibility. There might be users who are just lazy to update but I would still like to show them entries with the new enum values. I use the enum for multiple things like choosing the right color for something or displaying a certain icon. The default could then at least display a default color and icon. That should be better than simply not displaying anything. My main problem is that I deserialize a whole array of my structs so one faulty enum causes no entries to appear – xxtesaxx Dec 04 '17 at 13:42
  • Add some code how you are trying to do it. – PGDev Dec 05 '17 at 11:56

1 Answers1

4

You can implement init(from decoder: Decoder) and encode(to encoder: Encoder) and handle every case explicitly, i.e.

struct MyType: Codable {
    var name: String?
    var value: Value?

    enum CodingKeys: String, CodingKey {
        case name
        case value
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        let strValue = try values.decode(String.self, forKey: .value)
        //You need to handle every case explicitly
        switch strValue {
        case "someValue":
            value = Value.someValue
        case "someOtherValue":
            value = Value.someOtherValue
        default:
            value = Value.unknown(value: strValue)
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        if let val = value {
            //You need to handle every case explicitly
            switch val {
            case .someValue, .someOtherValue:
                try container.encode(String(describing: val), forKey: .value)
            case .unknown(let strValue):
                try container.encode(strValue, forKey: .value)
            }
        }
    }
}

enum Value {
    case someValue
    case someOtherValue
    case unknown(value: String)
}
PGDev
  • 23,751
  • 6
  • 34
  • 88
  • Ah so you mean instead of trying to put some custom serialization code in the enum, I do it in the struct. Thats a good idea. I could write a little String -> Value function in the enum and just call it with the value from inside the struct. Thanks, that helps me – xxtesaxx Dec 06 '17 at 04:12
  • Sure..accept the answer if it helped..happy coding – PGDev Dec 06 '17 at 04:13