You can drop the raw type for your Type
and make unknown case that handles associated value. But this comes at a cost. You somehow need the raw values for your cases. Inspired from this and this SO answers I came up with this elegant solution to your problem.
To be able to store the raw values, we will maintain another enum, but as private:
enum Type {
case text
case image
case document
case profile
case sign
case inputDate
case inputText
case inputNumber
case inputOption
case unknown(String)
// Make this private
private enum RawValues: String, Codable {
case text = "text"
case image = "image"
case document = "document"
case profile = "profile"
case sign = "sign"
case inputDate = "input_date"
case inputText = "input_text"
case inputNumber = "input_number"
case inputOption = "input_option"
// No such case here for the unknowns
}
}
Move the encoding
& decoding
part to extensions:
Decodable part:
extension Type: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// As you already know your RawValues is String actually, you decode String here
let stringForRawValues = try container.decode(String.self)
// This is the trick here...
switch stringForRawValues {
// Now You can switch over this String with cases from RawValues since it is String
case RawValues.text.rawValue:
self = .text
case RawValues.image.rawValue:
self = .image
case RawValues.document.rawValue:
self = .document
case RawValues.profile.rawValue:
self = .profile
case RawValues.sign.rawValue:
self = .sign
case RawValues.inputDate.rawValue:
self = .inputDate
case RawValues.inputText.rawValue:
self = .inputText
case RawValues.inputNumber.rawValue:
self = .inputNumber
case RawValues.inputOption.rawValue:
self = .inputOption
// Now handle all unknown types. You just pass the String to Type's unknown case.
// And this is true for every other unknowns that aren't defined in your RawValues
default:
self = .unknown(stringForRawValues)
}
}
}
Encodable part:
extension Type: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text:
try container.encode(RawValues.text)
case .image:
try container.encode(RawValues.image)
case .document:
try container.encode(RawValues.document)
case .profile:
try container.encode(RawValues.profile)
case .sign:
try container.encode(RawValues.sign)
case .inputDate:
try container.encode(RawValues.inputDate)
case .inputText:
try container.encode(RawValues.inputText)
case .inputNumber:
try container.encode(RawValues.inputNumber)
case .inputOption:
try container.encode(RawValues.inputOption)
case .unknown(let string):
// You get the actual String here from the associated value and just encode it
try container.encode(string)
}
}
}
Examples:
I just wrapped it in a container structure(because we'll be using JSONEncoder/JSONDecoder) as:
struct Root: Codable {
let type: Type
}
For values other than unknown case:
let rootObject = Root(type: Type.document)
do {
let encodedRoot = try JSONEncoder().encode(rootObject)
do {
let decodedRoot = try JSONDecoder().decode(Root.self, from: encodedRoot)
print(decodedRoot.type) // document
} catch {
print(error)
}
} catch {
print(error)
}
For values with unknown case:
let rootObject = Root(type: Type.unknown("new type"))
do {
let encodedRoot = try JSONEncoder().encode(rootObject)
do {
let decodedRoot = try JSONDecoder().decode(Root.self, from: encodedRoot)
print(decodedRoot.type) // unknown("new type")
} catch {
print(error)
}
} catch {
print(error)
}
I put the example with local objects. You can try with your REST API response.
Type
to be an optional. – Hbomb