Enum with Raw Value, Codable

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



Leave a reply



Submit