Codable enum with multiple keys and associated values - swift

I've seen answers on how to make an enum conform to Codable when all cases have an associated value, but I'm not clear on how to mix enums that have cases with and without associated values:
??? How can I use multiple variations of the same key for a given case?
??? How do I encode/decode cases with no associated value?
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 // ??? how can I accept "aaa", "AAA", and "a"?
case bbb
case 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)
return
}
// ???
// How do I decode the cases with no associated value?
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .ccc(let year):
try container.encode(year, forKey: .ccc)
default:
// ???
// How do I encode cases with no associated value?
}
}
}

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

Starting with Swift 5.5, enums with associated values gained the ability of having automatic conformance to Codable. See this swift-evolution proposal for more details about the implementation details.
So, this is enough for your enum:
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
No more CodingKeys, no more init(from:), or encode(to:)

Thanks #vadian for the great answer 🙏🏻
Another way how to implement custom Decodable / Encodable methods for any enum with associated value and (or) empty cases – using approach with the nestedContainer method called from the root container.
That way describes in the Swift Evolution proposal for Swift 5.5 the support for auto-synthesis of Codable conformance to enums with associated values.
All details and the next implementation I got from the proposal you can also take a look: https://github.com/apple/swift-evolution/blob/main/proposals/0295-codable-synthesis-for-enums-with-associated-values.md
I also extend example from the proposal to cover exactly author's question.
enum Command: Codable {
case load(String)
case store(key: String, Int)
case eraseAll
}
The encode(to:) implementation for Encodable would look as follows:
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .load(key):
var nestedContainer = container.nestedUnkeyedContainer(forKey: .load)
try nestedContainer.encode(key)
case let .store(key, value):
var nestedContainer = container.nestedContainer(keyedBy: StoreCodingKeys.self, forKey: .store)
try nestedContainer.encode(key, forKey: .key)
try nestedContainer.encode(value, forKey: .value)
case .eraseAll:
var nestedContainer = container.nestedUnkeyedContainer(forKey: .eraseAll)
try nestedContainer.encodeNil()
}
}
Please pay attention on some modifications: (1) for load and eraseAll cases I use nestedUnkeyedContainer instead suggested in proposal and eraseAll is the new case which I also added without an associated value.
and the init(from:) implementation for Decodable would look as follows:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.allKeys.count != 1 {
let context = DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Invalid number of keys found, expected one.")
throw DecodingError.typeMismatch(Command.self, context)
}
switch container.allKeys.first.unsafelyUnwrapped {
case .load:
let nestedContainer = try container.nestedUnkeyedContainer(forKey: .load)
self = .load(try nestedContainer.decode(String.self))
case .store:
let nestedContainer = try container.nestedContainer(keyedBy: StoreCodingKeys.self, forKey: .store)
self = .store(
key: try nestedContainer.decode(String.self, forKey: .key),
value: try nestedContainer.decode(Int.self, forKey: .value))
case .eraseAll:
_ = try container.nestedUnkeyedContainer(forKey: .eraseAll)
self = .eraseAll
}
}

Related

how do you set nil to codable enum when unexpected value in swift iOS

I'm using Codable for my WebRequest response which is returning some predefined string or number. So, I'm using Enums for those. But when some unexpected value arrive to Response at that my Codable fails to decode.
Here some code for better understanding.
class WebUser: Codable, Equatable {
static func == (lhs: WebUser, rhs: WebUser) -> Bool {
return lhs.id == rhs.id
}
...
var mobileNumberPrivacy: CommonPrivacyOption?
var emailPrivacy: CommonPrivacyOption?
var dobPrivacy: CommonPrivacyOption?
...
}
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
//Does not help this optional init function
/*init?(rawValue: Int) {
switch rawValue {
case 1: self = .privacyOnlyMe
case 2: self = .privacyPublic
case 3: self = .privacyFriends
case 4: self = .privacyFriendsOfFriends
default: return nil
}
}*/
}
but sometimes from WebServer I'm getting, 0 for dobPrivacy at that time I'm getting DecodingError.dataCorrupted exception with context Cannot initialize CommonPrivacyOption from invalid Int value 0
As I expect to dobPrivacy nil when I get other values then 1/2/3/4.
EDIT:
let dict1 = [
"id": 2,
"mobileNumberPrivacy": 3,
"emailPrivacy": 4,
"dobPrivacy": 0 // Works perfectly with 1
]
do {
let data1 = try JSONSerialization.data(withJSONObject: dict1, options: .prettyPrinted)
let user1 = try JSONDecoder().decode(WebUser.self, from: data1)
print("User1 created")
}
catch DecodingError.dataCorrupted(let context) {
print(context.codingPath)
print(context.debugDescription)
}
catch {
print(error.localizedDescription)
}
I'm using this same Codable WebUser object for Profile detail, search users and many more.
so may be some times one more key will not present in WebRequest's response.
I recommend writing a property wrapper that handles this problem for you.
Specifically, let's write a property wrapper named NilOnDecodingError that turns any DecodingError into nil.
Here's the declaration of NilOnDecodingError:
#propertyWrapper
public struct NilOnDecodingError<Wrapped> {
public init(wrappedValue: Wrapped?) {
self.wrappedValue = wrappedValue
}
public var wrappedValue: Wrapped?
}
We've defined it to wrap any type, storing an Optional.
Now we can conform it to Decodable when the Wrapped type is Decodable:
extension NilOnDecodingError: Decodable where Wrapped: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
wrappedValue = .some(try container.decode(Wrapped.self))
} catch is DecodingError {
wrappedValue = nil
}
}
}
We probably also want it to be Encodable when the Wrapped type is Encodable:
extension NilOnDecodingError: Encodable where Wrapped: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let value = wrappedValue {
try container.encode(value)
} else {
try container.encodeNil()
}
}
}
Now we can wrap the appropriate fields of WebUser:
class WebUser: Codable {
let id: String
#NilOnDecodingError
var mobileNumberPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var emailPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var dobPrivacy: CommonPrivacyOption?
}
For testing, we'll want to print the fields of the decoded user:
extension WebUser: CustomStringConvertible {
var description: String {
return """
WebUser(
id: \(id),
mobileNumberPrivacy: \(mobileNumberPrivacy.map { "\($0)" } ?? "nil"),
emailPrivacy: \(emailPrivacy.map { "\($0)" } ?? "nil")),
dobPrivacy: \(dobPrivacy.map { "\($0)" } ?? "nil")))
"""
}
}
Now we can try it out:
let json = """
{
"id": "mrugesh",
"mobileNumberPrivacy": 1,
"emailPrivacy": 2,
"dobPrivacy": 1000
}
"""
let user = try! JSONDecoder().decode(WebUser.self, from: json.data(using: .utf8)!)
print(user)
Output:
WebUser(
id: mrugesh,
mobileNumberPrivacy: privacyOnlyMe,
emailPrivacy: privacyPublic),
dobPrivacy: nil))
You need to create a custom Decodable initializer inside WebUser:
class WebUser: Codable {
var dobPrivacy: CommonPrivacyOption?
// Rest of your properties here.
enum CodingKeys: String, CodingKey {
case dobPrivacy
// Add a case for each property you want to decode here.
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Use optional try to decode your enum so that when the
// decode fails because of wrong Int value, it will assign nil.
dobPrivacy = try? container.decode(CommonPrivacyOption.self, forKey: .dobPrivacy)
}
}
Alternatively, you can implement the Decodable initializer inside CommonPrivacyOption and add an additional case unknown like so:
enum CommonPrivacyOption: Int, Codable {
case privacyOnlyMe = 1
case privacyPublic, privacyFriends, privacyFriendsOfFriends
case unknown
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(Int.self)
// Try to initialize Self from value, if
// value is not 1, 2, 3, or 4, initialize Self to
// the unknown case.
self = .init(rawValue: value) ?? .unknown
}
}
It looks to me like the compiler selects the wrong init for the enum types, instead of init(rawValue) it uses init(from:) that is the one for decoding (which in a way makes sense)
Here is a solution where we override this behaviour by using a custom init(from) in WebUser that decodes the raw values and then creates an enum item
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .mobileNumberPrivacy), let mobileNumberPrivacy = CommonPrivacyOption(rawValue: value) {
self.mobileNumberPrivacy = mobileNumberPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .emailPrivacy), let emailPrivacy = CommonPrivacyOption(rawValue: value) {
self.emailPrivacy = emailPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .dobPrivacy), let dobPrivacy = CommonPrivacyOption(rawValue: value) {
self.dobPrivacy = dobPrivacy
}
}
Below is a small example
extension WebUser: CustomStringConvertible {
var description: String {
"Mobile: \(mobileNumberPrivacy?.rawValue), email: \(emailPrivacy?.rawValue), dob: \(dobPrivacy?.rawValue)"
}
}
let data = """
{
"mobileNumberPrivacy": 1,
"dobPrivacy": 0
}
""".data(using: .utf8)!
do {
let result = try JSONDecoder().decode(WebUser.self, from: data)
print(result)
} catch {
print(error)
}
Mobile: Optional(1), email: nil, dob: nil
Of course if you can change your mind about 0 being translated to nil then I would suggest you extend the enum to support the 0 value
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case none = 0
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
}
Then it should work out of the box and you don't need to write any custom code.
I liked #alobaili's answer since it's simple and provided a good solution. One thing I wanted to improve is to make it more generic, so that any Decodable (and Codable) could do it with less code to write.
extension Decodable where Self: RawRepresentable, RawValue: Decodable {
static func initializedOptionalWith(decoder: Decoder, defaultValue: Self) throws -> Self {
let container = try decoder.singleValueContainer()
let value = try container.decode(RawValue.self)
return .init(rawValue: value) ?? defaultValue
}
}
Usage:
init(from decoder: Decoder) throws {
self = try .initializedOptionalWith(decoder: decoder, defaultValue: .unknown)
}
Thank you Rob Mayoff for a great answer. I would like to add just one thing - if the property is missing, decoding fails even if the property is optional. To prevent this failure, add the following extension:
extension KeyedDecodingContainer {
func decode<T>(_ type: NilOnDecodingError<T>.Type, forKey key: Self.Key) throws -> NilOnDecodingError<T> where T: Decodable {
try decodeIfPresent(type, forKey: key) ?? NilOnDecodingError<T>(wrappedValue: nil)
}
}

Polymorphically decode a sub-object of a KeyedDecodingContainer in Swift

The following code attempts to create a Codable type AnyBase that would allow you to encode and decode various subclasses of Base. As written, in fails, because it tries to lookup the type objects by a string code, using getTypeFromCode. But if you use the commented-out part instead with hard-coded types, it prints "Sub1", as desired.
Is there a way to adjust this to use something like getTypeFromCode and eliminate the hard-coded types from init?
class Base: Codable {
var foo: Int = 1
var codingTypeKey: String { fatalError("abstract") }
}
class Sub1: Base {
var sub1Prop: Int = 2
override var codingTypeKey: String { "sub1" }
enum CodingKeys: CodingKey {
case sub1Prop
}
override init() {
super.init()
}
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
sub1Prop = try c.decode(Int.self, forKey: .sub1Prop)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(sub1Prop, forKey: .sub1Prop)
try super.encode(to: encoder)
}
}
class Sub2: Base {
var sub2Prop: Int = 2
override var codingTypeKey: String { "sub2" }
}
func getTypeFromCode(_ typeCode: String) -> Base.Type {
if typeCode == "sub1" { return Sub1.self }
else if typeCode == "sub2" { return Sub2.self }
else { fatalError("bad type code") }
}
class AnyBase: Codable {
let base: Base
init(_ b: Base) { base = b }
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let typeCode = try c.decode(String.self, forKey: .type)
/*if typeCode == "sub1" {
self.base = try c.decode(Sub1.self, forKey: .base)
} else if typeCode == "sub2" {
self.base = try c.decode(Sub2.self, forKey: .base)
} else {
fatalError("bad type code")
}*/
let typeObj = getTypeFromCode(typeCode)
self.base = try c.decode(typeObj, forKey: .base)
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(base.codingTypeKey, forKey: .type)
try c.encode(base, forKey: .base)
}
enum CodingKeys: CodingKey {
case base, type
}
}
// To to round-trip encode/decode and get the right object back...
let enc = JSONEncoder()
let dec = JSONDecoder()
let sub = Sub1()
let any = AnyBase(sub)
let data = try! enc.encode(any)
let str = String(data:data, encoding:.utf8)!
print(str)
let any2 = try! dec.decode(AnyBase.self, from: data)
print(type(of:any2.base))
Instead of getTypeFromCode (_:) method, you can create an enum BaseType like,
enum BaseType: String {
case sub1, sub2
var type: Base.Type {
switch self {
case .sub1: return Sub1.self
case .sub2: return Sub2.self
}
}
}
Now, in init(from:) get the BaseType using typeCode as rawValue, i.e.
class AnyBase: Codable {
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let typeCode = try c.decode(String.self, forKey: .type)
if let baseType = BaseType(rawValue: typeCode) {
self.base = try c.decode(baseType.type, forKey: .base)
} else {
fatalError("bad type code")
}
}
//rest of the code...
}

Codable conformance for nested Enum?

I'm a bit tangled up on using Codable with nested enums. Suppose I have the following enum:
enum ItemStatus {
enum Foo {
case a
case b(Double, Double)
case c
}
enum Bar {
case x(String)
case y
case z(Int)
}
}
extension ItemStatus: Codable {
init(from decoder: Decoder) throws {
}
func encode(to encoder: Encoder) throws {
}
}
For normal enums, I'm implementing Codable like this. It works, but I'm not sure how to implement Codable for the top-level enum (in this case, ItemStats), and my gut tells me there is a more efficient way to write this anyway:
extension ItemStatus.Bar {
init?(rawValue: String?) {
guard let val = rawValue?.lowercased() else {
return nil
}
switch val {
case "x": self = .x("")
case "y": self = .y
case "z": self = .z(0)
default: return nil
}
}
private var typeStr: String {
guard let label = Mirror(reflecting: self).children.first?.label else {
return .init(describing: self)
}
return label
}
}
extension ItemStatus.Bar : Codable {
private enum CodingKeys: String, CodingKey {
case type
case xVal
case zVal
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let type = try? container.decode(String.self, forKey: .type) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Could not get type of Bar."))
}
guard let base = ItemStatus.Bar(rawValue: type) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Could not init Bar with value: \(type)"))
}
switch base {
case .x:
let val = try container.decode(String.self, forKey: .xVal)
self = .x(val)
case .z:
let val = try container.decode(Int.self, forKey: .zVal)
self = .z(val)
default:
self = base
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.typeStr, forKey: .type)
switch self {
case .x(let val):
try container.encode(val, forKey: .xVal)
case .z(let val):
try container.encode(val, forKey: .zVal)
default: ()
}
}
}

How to save an enum conforming to Codable protocol to UserDefaults?

Follow up from my previously question:
I managed to make my enum to conform to Codable protocol, implemented init() and encode() and it seems to work.
enum UserState {
case LoggedIn(LoggedInState)
case LoggedOut(LoggedOutState)
}
enum LoggedInState: String {
case playing
case paused
case stopped
}
enum LoggedOutState: String {
case Unregistered
case Registered
}
extension UserState: Codable {
enum CodingKeys: String, CodingKey {
case loggedIn
case loggedOut
}
enum CodingError: Error {
case decoding(String)
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let loggedIn = try? values.decode(String.self, forKey: .loggedIn) {
self = .LoggedIn(LoggedInState(rawValue: loggedIn)!)
}
else if let loggedOut = try? values.decode(String.self, forKey: .loggedOut) {
self = .LoggedOut(LoggedOutState(rawValue: loggedOut)!)
}
else {
throw CodingError.decoding("Decoding failed")
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .LoggedIn(value):
try container.encode(value.rawValue, forKey: .loggedIn)
case let .LoggedOut(value):
try container.encode(value.rawValue, forKey: .loggedOut)
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let state = UserState.LoggedIn(.playing)
UserDefaults.standard.set(state, forKey: "state")
}
}
My problem is I don't know how to save it to UserDefaults. If I just save it like I do now I get the following error when running the app:
[User Defaults] Attempt to set a non-property-list object Codable.UserState.LoggedIn(Codable.LoggedInState.playing) as an NSUserDefaults/CFPreferences value for key state
2018-01-20 19:06:26.909349+0200 Codable[6291:789687]
From UserDefaults reference:
A default object must be a property list—that is, an instance of (or
for collections, a combination of instances of) NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData.
So you should encode state manually and store it as data:
if let encoded = try? JSONEncoder().encode(state) {
UserDefaults.standard.set(encoded, forKey: "state")
}
Then, to read it back:
guard let data = UserDefaults.standard.data(forKey: "state") else { fatalError() }
if let saved = try? JSONDecoder().decode(UserState.self, from: data) {
...
}
Swift 4+. Enum with a normal case, and a case with associated value.
Enum's code:
enum Enumeration: Codable {
case one
case two(Int)
private enum CodingKeys: String, CodingKey {
case one
case two
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(String.self, forKey: .one), value == "one" {
self = .one
return
}
if let value = try? container.decode(Int.self, forKey: .two) {
self = .two(value)
return
}
throw _DecodingError.couldNotDecode
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .one:
try container.encode("one", forKey: .one)
case .two(let number):
try container.encode(number, forKey: .two)
}
}
}
Write to UserDefaults:
let one1 = Enumeration.two(442)
if let encoded = try? encoder.encode(one1) {
UserDefaults.standard.set(encoded, forKey: "savedEnumValue")
}
Read from UserDefaults:
if let savedData = UserDefaults.standard.object(forKey: "savedEnumValue") as? Data {
if let loadedValue = try? JSONDecoder().decode(Enumeration.self, from: savedData) {
print(loadedValue) // prints: "two(442)"
}
}
Enum should have rawValue to have ability to be saved as Codable object. Your enum cases have associated values, so they cannot have rawValue.
Article with good explanation
My explanation
Lets imagine that you can save your object .LoggedIn(.playing) to UserDefaults. You want to save UserState's .LoggedIn case. There are 2 main questions:
What should be saved to the storage? Your enum case does not have any value, there is nothing to save. You may think that it has associated value, it can be saved to the storage. So the 2nd question.
(imagine that you saved it to storage) After you get saved value from storage, how are you going to determine what the case is it? You may think that it can be determined by the type of associated value, but what if you have lots of cases with the same associated value types? So the compiler does, it does not know what to do in this situation.
Your problem
You are trying to save the enum's case itself, but the saving to the UserDefaults does not convert your object by default. You can try to encode your .LoggedIn case to Data and then save the resulting data to UserDefaults. When you will need to get saved value you will get the data from storage and decode the enum's case from the data.

How to use Codable protocol for an enum with other enum as associated value (nested enum)

I asked a question yesterday on how to save a nested enum inside UserDefaults.
I am trying to use the Codable protocol for this, but not sure if I am doing it correctly.
Here is again the enum I want to store -> UserState:
enum UserState {
case LoggedIn(LoggedInState)
case LoggedOut(LoggedOutState)
}
enum LoggedInState: String {
case playing
case paused
case stopped
}
enum LoggedOutState: String {
case Unregistered
case Registered
}
Here are the steps I did so far:
Conform to Codable protocol and specify which are the keys we use for encode/decode:
extension UserState: Codable {
enum CodingKeys: String, CodingKey {
case loggedIn
case loggedOut
}
enum CodingError: Error {
case decoding(String)
}
}
Added initializer for decode:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let loggedIn = try? values.decode(String.self, forKey: .loggedIn) {
self = .LoggedIn(LoggedInState(rawValue: loggedIn)!)
}
if let loggedOut = try? values.decode(String.self, forKey: .loggedOut) {
self = .LoggedOut(LoggedOutState(rawValue: loggedOut)!)
}
throw CodingError.decoding("Decoding failed")
}
Added encode method:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .LoggedIn(value):
try container.encode(value, forKey: .loggedIn) // --> Ambiguous reference to member 'encode(_:forKey:)'
case let .LoggedOut(value):
try container.encode(value, forKey: .loggedOut) // --> Ambiguous reference to member 'encode(_:forKey:)'
}
}
The encode method gives me the above two errors. Not sure right now what I am doing wrong or if I am on the right track.
Any idea what I am doing wrong and what causes these two ambiguous errors ?
The associated value is LoggedInState or LoggedOutState but you have to encode its rawValue (String):
case let .LoggedIn(value):
try container.encode(value.rawValue, forKey: .loggedIn)
case let .LoggedOut(value):
try container.encode(value.rawValue, forKey: .loggedOut)
}
according to the decode method where you're creating the enum cases from the rawValue.