JsonDecoder with optional enum

I'm not sure how to use JsonDecoder with an optional enum.

This scenario would occur when the client and server agree on an enum, then the server starts returning an additional enum value.


//e.g. enum defined on deployed app

enum ColorType: Int, Codable {

case red = 0

case green = 1

}


struct ColorResponse: Codable {

var color: ColorType?

}


//server starts returning new enum case blue = 2


let jsonString = "{ \"color\": 2 }"

let jsonDecoder = JSONDecoder()

let jsonData = jsonString.data(using: .utf8)

let response = try? jsonDecoder.decode(ColorResponse.self, from: jsonData!)


My assumption was that response would be an instance of ColorResponse with color == nil

Instead, JsonDecoder throws a DataCorrupted error

"Cannot initialize ColorType from invalid Int value 2"


I don't want to lose strong typing by changing ColorResponse to var color: Int


Thanks,

casey

Effectively, there is a type conflict. Because you did not encode a ColorResponse (JSONEncoder().encode(aColor).


Have a look here for detailed tutorial:

h ttps://www.raywenderlich.com/172145/encoding-decoding-and-serialization-in-swift-4


If you want to encode as String, could try this:

        let jsonString = "{ \"color\": 2 }"
        let jsonData = jsonString.data(using: .utf8)
        do {
            if let aDictionary  = try JSONSerialization.jsonObject(with: jsonData!, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
                if let rawColor = aDictionary["color"] {
                    let x = ColorType(rawValue: rawColor as! Int) // x can be nil
                         // You can asign it to a ColorType?

                }
            }
        }
        catch {
            print("Error") /
        }

Thanks, but I'm really looking for a JsonDecoder solution.

This is what I've got so far.

Still trying some other techniques.


struct ColorResponse: Codable {

var color: SafeIntEnum<ColorType>

}


public enum SafeIntEnum<T: RawRepresentable>: Codable where T.RawValue == Int {

case known(T)

case unknown(Int)

init(from decoder: Decoder) throws {

let singleValue = try decoder.singleValueContainer()

let value = try singleValue.decode(Int.self)

if let value = T.init(rawValue: value) {

self = .known(value)

return

}

self = .unknown(value)

}

public func encode(to encoder: Encoder) throws {

throw NSError()

}

var value: T? {

switch self {

case .known(let theEnum):

return theEnum

case .unknown(_):

return nil

}

}

var intValue: Int {

switch self {

case .known(let theEnum):

return theEnum.rawValue

case .unknown(let intVal):

return intVal

}

}

}

OK. Your initial post was about json decoder. If this is no more your point, please close this thread.

Another option is to override RawRepresentable to default to a .nil enum case when a match fails to occur.


enum ColorType: Int, CoreApiEnum {

case `nil` = -1 //client side only

case red = 0

case green = 1

}


extension ColorType: RawRepresentable {

typealias RawValue = Int

init?(rawValue: RawValue) {

switch rawValue {

case 0: self = .red

case 1: self = .green

default: self = .nil

}

}

var rawValue: RawValue {

switch self {

case .red: return 0

case .green: return 1

default: return -1

}

}

}

Think I'm going with the following approach.

Let JsonDecoder decode to raw Int or Int?

And then wrap it to look like an optional enum on the client.


struct ColorResponse: Codable {

private let colorRaw: Int

enum CodingKeys: String, CodingKey {

case colorRaw = "color"

}

var color: ColorType? {

return ColorType(rawValue: colorRaw)

}

}

JsonDecoder with optional enum
 
 
Q