Search code examples
jsonswiftenumsdecodable

Swift - Have an unknown case for enum decoded from json string that is not included in cases


For a given JSON like below:

{
    "store": {
        "animals": [
            {
                "type": "dog"
            },
            {
                "type": "cat"
            }
        ]
    }
}

I can parse it with enum for type like following:

final class AnimalStore: Decodable {
    let store: Store
}

extension AnimalStore {
    struct Store: Decodable {
        let animals: [Animal]
    }
}

extension AnimalStore.Store {
    struct Animal: Decodable {
        let type: AnimalType?
    }
}

extension AnimalStore.Store.Animal {
    enum AnimalType: String, Decodable {
        case dog = "dog"
        case cat = "cat"
        //case unknown = how such a case could be implemented?
    }
}

And also since it is optional; it would work fine if type key value pair would be missing from json.

But I would like to have another case, lets call it unknown so that if any given type is not dog or cat (string being something else),type would be initialised as unknown. Right now it crashes if a type, other than dog or cat is given.

How initialising with another type other than the ones given can be implemented with enum?

In other words, for a given type like: "type": "bird" I would like type to be initialised as unknown.


Solution

  • Add the enum case with a string, you may as well use "unknown".

    To convert non-matching strings to unknowns, you have to manually implement init(from decoder: Decoder) at some point, either in your Animal or in AnimalType. I'd favour using AnimalType so that you don't have to manually decode any of the other properties of Animal.

    enum AnimalType: String, Decodable {
        case dog = "dog"
        case cat = "cat"
        case unknown = "unknown"
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            let string = try container.decode(String.self)
            self = AnimalType(rawValue: string) ?? .unknown
        }
    }
    

    If you did it in Animal, you'd need something like:

    // Decode everything else...
    type = try? decoder.decode(AnimalType.self, forKey: .type) ?? .unknown