Swift: Enum codable how to get raw value
One solution is to add two computed properties in the enum to get the associated values.
var stringValue : String? {
guard case let .string(value) = self else { return nil }
return value
}
var intValue : Int? {
guard case let .integer(value) = self else { return nil }
return value
}
And use it
answer.id?.intValue
Enum with Raw value, Codable
Automatic Codable
synthesis is “opt-in,” i.e. you have to declare the
conformance explicitly:
enum Occupation: String, Codable { // <--- HERE
case designer = "Designer"
case engineer = "Engineer"
}
public struct SteveJobs: Codable {
let name: String
let occupation: Occupation
}
See SE-0166 Swift Archival & Serialization
By adopting these protocols, user types opt in to this system.
The same is true for automatic Hashable
and Equatable
synthesis,
compare Requesting synthesis is opt-in in SE-0185, where
some reasons are listed:
The syntax for opting in is natural; there is no clear analogue in
Swift today for having a type opt out of a feature.It requires users to make a conscious decision about the public API
surfaced by their types. Types cannot accidentally "fall into"
conformances that the user does not wish them to; a type that does not
initially support Equatable can be made to at a later date, but the
reverse is a breaking change.The conformances supported by a type can be clearly seen by examining
its source code; nothing is hidden from the user.We reduce the work done by the compiler and the amount of code
generated by not synthesizing conformances that are not desired and
not used.
How do I make an enum Decodable in Swift?
It's pretty easy, just use String
or Int
raw values which are implicitly assigned.
enum PostType: Int, Codable {
case image, blob
}
image
is encoded to 0
and blob
to 1
Or
enum PostType: String, Codable {
case image, blob
}
image
is encoded to "image"
and blob
to "blob"
This is a simple example how to use it:
enum PostType : Int, Codable {
case count = 4
}
struct Post : Codable {
var type : PostType
}
let jsonString = "{\"type\": 4}"
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(Post.self, from: jsonData)
print("decoded:", decoded.type)
} catch {
print(error)
}
Update
In iOS 13.3+ and macOS 15.1+ it's allowed to en-/decode fragments – single JSON values which are not wrapped in a collection type
let jsonString = "4"
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(PostType.self, from: jsonData)
print("decoded:", decoded) // -> decoded: count
} catch {
print(error)
}
In Swift 5.5+ it's even possible to en-/decode enums with associated values without any extra code. The values are mapped to a dictionary and a parameter label must be specified for each associated value
enum Rotation: Codable {
case zAxis(angle: Double, speed: Int)
}
let jsonString = #"{"zAxis":{"angle":90,"speed":5}}"#
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(Rotation.self, from: jsonData)
print("decoded:", decoded)
} catch {
print(error)
}
Codable enum with default case in Swift 4
You can extend your Codable
Type and assign a default value in case of failure:
enum Type: String {
case text,
image,
document,
profile,
sign,
inputDate = "input_date",
inputText = "input_text" ,
inputNumber = "input_number",
inputOption = "input_option",
unknown
}
extension Type: Codable {
public init(from decoder: Decoder) throws {
self = try Type(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown
}
}
edit/update:
Xcode 11.2 • Swift 5.1 or later
Create a protocol that defaults to last case of a CaseIterable & Decodable
enumeration:
protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection { }
extension CaseIterableDefaultsLast {
init(from decoder: Decoder) throws {
self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? Self.allCases.last!
}
}
Playground testing:
enum Type: String, CaseIterableDefaultsLast {
case text, image, document, profile, sign, inputDate = "input_date", inputText = "input_text" , inputNumber = "input_number", inputOption = "input_option", unknown
}
let types = try! JSONDecoder().decode([Type].self , from: Data(#"["text","image","sound"]"#.utf8)) // [text, image, unknown]
How do you make an enum decodable by its case name and not its raw value?
I looked into it and the problem here is that what you see in the JSON result is an encoded value, not a key. Consequently, adding CodingKeys
won't help.
A slightly complicated solution uses a custom protocol and a corresponding extension to achieve the goal.
With that, you can declare:
enum Test: String, CaseNameCodable {
case one = "Number One"
case two = "Number Two"
}
and it would do what you need.
A complete working example is sketched below (works for me in a Playground in Xcode 11.2):
import Foundation
// A custom error type for decoding...
struct CaseNameCodableError: Error {
private let caseName: String
init(_ value: String) {
caseName = value
}
var localizedDescription: String {
#"Unable to create an enum case named "\#(caseName)""#
}
}
//
// This is the interesting part:
//
protocol CaseNameCodable: Codable, RawRepresentable , CaseIterable {}
extension CaseNameCodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(String.self)
guard let raw = Self.allCases.first(where: { $0.caseName == value })?.rawValue else { throw CaseNameCodableError(value) }
self.init(rawValue: raw)!
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(caseName)
}
private var caseName: String {
return "\(self)"
}
}
//
// Now you can use the protocol CaseNameCodable just like you
// would use Codable (on RawRepresentable enums only)
//
enum Test: String, CaseNameCodable {
case one = "Number One"
case two = "Number Two"
}
// EXAMPLE:
// Create a test value
let testValue = Test.one
// encode it and convert it to a String
let jsonData = try! JSONEncoder().encode(testValue)
let jsonString = String(data: jsonData, encoding: .utf8)!
print (jsonString) // prints: "one"
// decode the same data to produce a decoded enum instance
let decodedTestValue = try JSONDecoder().decode(Test.self, from: jsonData)
print(decodedTestValue.rawValue) // prints: Number One
Codable enum with multiple keys and associated values
Use the assumed raw string values of the init method as (string) value of the enum case
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
init?(rawValue: String?) {
guard let val = rawValue?.lowercased() else {
return nil
}
switch val {
case "aaa", "a":
self = .aaa
case "bbb":
self = .bbb
case "ccc":
self = .ccc(0)
default: return nil
}
}
// Codable
private enum CodingKeys: String, CodingKey { case aaa, bbb, ccc }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(Int.self, forKey: .ccc) {
self = .ccc(value)
} else if let aaaValue = try? container.decode(String.self, forKey: .aaa), ["aaa", "AAA", "a"].contains(aaaValue) {
self = .aaa
} else if let bbbValue = try? container.decode(String.self, forKey: .bbb), bbbValue == "bbb" {
self = .bbb
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Data doesn't match"))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .aaa: try container.encode("aaa", forKey: .aaa)
case .bbb: try container.encode("bbb", forKey: .bbb)
case .ccc(let year): try container.encode(year, forKey: .ccc)
}
}
}
The Decoding Error is quite generic. You can throw more specific errors for each CodingKey
Decoding enum in Swift 5 - This CANNOT be this hard
This all you need here.
struct Contract: Codable {
var contractType: ContractType
enum ContractType: Int, Codable {
case scavenger
}
}
do {
let contract = try JSONDecoder().decode([Contract].self, from: json)
print(contract.contractType)
} catch {
print(error)
}
Related Topics
Outline Uilabel Text in Uilabel Subclass
Custom Mkoverlayrenderer Drawmaprect Function Not Drawing Polygons
Ruby: Loaderror - Library Not Found for Class Digest::Sha1 -- Digest/Sha1
iOS Swift Flood Fill Algorithm
iOS PDFkit Displaymode = Singlepage Only Shows the First Page of the PDF
How to Read References Given by Ptr_Refs in iOS
Dyld: Library Not Loaded: @Rpath/Mydsk.Framework/Mydsk -> Swift iOS 8.0
Nsfetchedresultscontroller Swift Sections
Displaying Instances of View Controllers Within Xcode Without Disturbing the Current Hierarchy
Swift Calling Setnavigationbarhidden But View Wont Move to Top
How to Make Async/Await in Swift
How to Properly Handle a Nil Uiapplication.Sharedapplication().Keywindow
Swift 3 Filter Array of Dictionaries by String Value of Key in Dictionary
Save Video to a Custom Album Using Photos Framework in iOS
Draw Button on Top of Avplayer
Swiftui Card Flip Animation with Two Views, One of Which Is Embedded Within a Stack
How to Set the Collection View Cell Size Exactly Equal to the Collection View in iOS
Circular Button Becomes Rounded Rectangle After Size Increase