enum encoded value is nil while storing the class object in UserDefaults. Codable Protocol is already inherited - swift

I am new to iOS and trying to store User object in UserDefaults. So that when the app is launched again, I can check user type and based on it, I need to navigate to relevant screen.
For that, I have created a User class as below (Codable) and it has one userType enum property!
enum UserType: Int, Codable {
case userType1 = 0
case userType2 = 1
case notDetermined = 2
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(Int.self)
self = UserType(rawValue: label) ?? .notDetermined
}
}
class User: Codable {
public var userFullName: String? = ""
public var userType: UserType? //= .notDetermined
enum CodingKeys: String, CodingKey {
case userFullName
}
}
In my view Controller class, I am creating a new instance for User object and trying to store in user defaults as below:
let newUser = User()
newUser.userFullName = "Test"
newUser.userType = userTypeBtn.isSelected ? .userType1 : .userType2
when I print the newUser's userType, I can see proper value whichever is selected. But after that, when I am trying to store it in userDefaults as below, it returns nil for userType property.
do {
let encoded = try JSONEncoder().encode(newValue)
UserDefaults.standard.set(encoded, forKey: UserDefaultKey.currentUser)
UserDefaults.standard.sync()
} catch {
print("Unable to Encode User Object: (\(error))")
}
when I tried to print this encoded variable, and decoded it in console
JSONDecoder().decode(User.self, from: encoded).userType
it prints nil.
Please help me how can I store optional enum property in UserDefaults and retrieve it when needed using Codable

You should include userType in your CodingKeys enum:
enum CodingKeys: String, CodingKey {
case userFullName
case userType
}
Or just delete the CodingKeys enum entirely, since by default, all the properties are included as coding keys. The keys in the CodingKeys enum determines what the synthesised Codable implementation will encode and decode. If you don't include userType, userType will not be encoded, so it will not be stored into UserDefaults.
I am not getting it from Server and userType is an external property outside the JSON response
This is fine, because userType is optional. If the JSON does not have the key, it will be assigned nil. This might be a problem if you are also encoding User and sending it to the server, and that the server can't handle extra keys in the request, in which case you need two structs - one for storing to/loading from UserDefaults, one for parsing/encoding server response/request.
Remember to encode a new User to UserDefaults when you try this out, since the old one still doesn't have the userType encoded with it.

Observations
Having a custom implementation for Decodable part of enum UserType: Int, Codable is probably not the best idea. Swift compiler supports encoding/decoding enum X: Int out of the box without having you to write custom implementation for it. (In fact, starting with Swift 5.5, Swift compiler can now do this for enums that have cases with associated values as well.)
You should try to avoid having cases like .notDetermined. Either user has a type that's well defined or user.type is nil. You can easily define convenience getters on user itself to know about it's type.
Swift allows nesting of types, so having User.Kind instead of UserType is more natural in Swift.
Following implementation takes care of all of these points.
import Foundation
class User: Codable {
enum Kind: Int, Codable {
case free = 1
case pro = 2
}
public var fullName: String?
public var kind: Kind?
}
let newUser = User()
newUser.fullName = "Test"
newUser.kind = .free
do {
let encoded = try JSONEncoder().encode(newUser)
UserDefaults.standard.set(encoded, forKey: "appUser")
if let fetched = UserDefaults.standard.value(forKey: "appUser") as? Data {
let decoded = try JSONDecoder().decode(User.self, from: fetched)
print(decoded)
}
}
Above code includes definition, construction, encodeAndStore, fetchAndDecode and it does everything you need without any custom implementation.
Bonus
Above code does not print a nice description for the User. For that, you can add CustomStringConvertible conformance like this.
extension User: CustomStringConvertible {
var description: String {
"""
fullName: \(fullName ?? "")
kind: \(kind?.description ?? "")
"""
}
}
extension User.Kind: CustomStringConvertible {
var description: String {
switch self {
case .free: return "free"
case .pro: return "pro"
}
}
}
If you try print(decoded) after implementing this, you will clearly see what you want to see for User instance.
User.kind can be nil and I don't want to handle it with if let every time I need to check this from different screens in the app.
No worries, it can be simplified to this.
extension User {
var isFreeUser: Bool { kind == .free }
var isProUser: Bool { kind == .pro }
}

Related

Is there a shorter way of declaring CodingKeys?

Say you have a struct for a model of your API response. Let's say it has 50 members. However, 5-7 members are non-standard casing, you could have AUsernAme or _BTmember, but the rest are all snake case credit_score or status_code.
Rather than writing all members like this:
struct MyStruct {
let aUserName: String
// +50 more...
private enum CodingKeys: String, CodingKey {
case aUserName = "AUsernAme"
// +50 more...
}
}
Is there a way that we can write it like this?
struct MyStruct {
#CodingKey("AUsernAme") let aUserName: String
let creditScore: Int
// +50 more ...
}
Edit: I guess this is not possible with the current Swift version, but does anyone know if this would somehow be included in the future versions of Swift?
The solution which Sweeper provided is a great solution to your problem, but IMO it may display great complexity to your problem and to the next developers who will read this code.
If I were you, I would just stick to writing all the CodingKeys for simplicity. If your worry is writing a lot of lines of cases, you can write all the cases that doesn't need custom keys in one line and just add the keys with unusual/non-standard casing on new lines:
case property1, property2, property3, property4, property5...
case property50 = "_property50"
And since you mentioned that the rest are in snake case, not sure if you know yet, but we have JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase.
Hope this helps `tol! :)
How about setting a custom keyDecodingStrategy just before you decode instead?
struct AnyCodingKey: CodingKey, Hashable {
var stringValue: String
init(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
}
let mapping = [
"AUsernAme": "aUserName",
// other mappings...
]
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
let key = codingPath[0].stringValue
guard let mapped = mapping[key] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
})
This assumes your JSON has a single level flat structure. You can make this into an extension:
extension JSONDecoder.KeyDecodingStrategy {
static func mappingRootKeys(_ dict: [String: String]) -> JSONDecoder.KeyDecodingStrategy {
return .custom { codingPath in
let key = codingPath[0].stringValue
guard let mapped = dict[key] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
}
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .mappingRootKeys(mapping)
If your JSON has more levels, you can change the type of the dictionary to [JSONPath: String], where JSONPath is a type that you can create that represents a key in a nested JSON. Then add a bit of code that converts the coding path, which is just an array of coding keys, to JSONPath. This should not be hard to write on your own.
A simple way is to just use [AnyCodingKey] as JSONPath, but there are many other ways too, and I encourage you to experiment and find the one you like the best.
typealias JSONPath = [AnyCodingKey]
extension AnyCodingKey {
init(codingKey: CodingKey) {
if let int = codingKey.intValue {
self.init(intValue: int)
} else {
self.init(stringValue: codingKey.stringValue)
}
}
}
extension JSONDecoder.KeyDecodingStrategy {
static func mappingRootKeys(_ dict: [JSONPath: String]) -> JSONDecoder.KeyDecodingStrategy {
return .custom { codingPath in
guard let mapped = dict[codingPath.map(AnyCodingKey.init(codingKey:))] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
}
}
}
let mapping = [
[AnyCodingKey(stringValue: "AUsernAme")]: "aUserName"
]
It is not possible to use a property wrapper for this. Your property wrapper #CodingKey("AUsernAme") let aUserName: String will be compiled to something like this (as per here):
private var _aUserName: CodingKey<String> = CodingKey("AUsernAme")
var aUserName: String {
get { _aUserName.wrappedValue }
set { _aUserName.wrappedValue = newValue }
}
There are two main problems with this:
Assuming you don't want to write init(from:) for all the 50+ properties in MyStruct, code will be synthesised to decode it, assigning to its _aUserName property. You only have control over the init(from:) initialiser of the CodingKey property wrapper, and you cannot do anything about how MyStruct is decoded in there. If MyStruct is contained in another struct:
struct AnotherStruct: Decodable {
let myStruct: MyStruct
}
Then you can indeed control the coding keys used to decode myStruct by marking it with a property wrapper. You can do whatever you want in the decoding process by implementing the property wrapper's init(from:), which brings us to the second problem:
The coding key you pass to the CodingKey property wrapper is passed via an initialiser of the form init(_ key: String). But you control the decoding via the initialiser init(from decoder: Decoder) because that is what will be called when the struct is decoded. In other words, there is no way for you to send the key mappings to the property wrapper.

When to use CodingKeys in Decodable(Swift)

Let's say I want to decode a Person struct as follows.
struct Person: Decodable {
let firstName: String
let lastName: String
let age: Int: String
}
I understand that the data can be decoded only with above. Therefore if I'm not changing the properties to a custom name if there no difference between the above and below implementation?
Further is there other cases where you want to use CodingKeys? I'm confused when they are necessary other than for renaming purposes.
struct Person: Decodable {
let firstName: String
let lastName: String
let age: Int: String
}
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case age
}
First of all there is a make-or-break rule for using CodingKeys:
You can omit CodingKeys completely if the JSON – or whatever Codable conforming format – keys match exactly the corresponding properties (like in your example) or the conversion is covered by an appropriate keyDecodingStrategy.
Otherwise you have to specify all CodingKeys you need to be decoded (see also reason #3 below).
There are three major reasons to use CodingKeys:
A Swift variable/property name must not start with a number. If a key does start with a number you have to specify a compatible CodingKey to be able to decode the key at all.
You want to use a different property name.
You want to exclude keys from being decoded for example an id property which is not in the JSON and is initialized with an UUID constant.
And CodingKeys are mandatory if you implement init(from decoder to decode a keyed container.
You can use CodingKeys in different ways for example, when you know that at least one of the name of values that you are expecting in your JSON is actually different from your "let or var" name.
Example:
struct Person: Decodable {
let firstName: String
let lastName: String
let age: Int: String
}
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName
case age
}
Other case is when you are using class inheritance.
In conclusion, if you are absolutely sure that you are using the same variable name as your encoding key(JSON), you can omit it (but if you want to put it, it doesn't matter), but if there's a difference, maybe a change of your codingKeys like an uppercase or using different words, you should use the enum to map the correct key with the variable name.
CodingKeys can be extremely helpful if you have a JSON with arbitrary number of coding keys (also called dynamic keys). Here is an example.
import UIKit
// Consider JSON with infinite number of keys: "S001", "S002" and so on
let jsonData = """
{
"S001": {
"firstName": "Tony",
"lastName": "Stark"
},
"S002": {
"firstName": "Peter",
"lastName": "Parker"
},
"S003": {
"firstName": "Bruce",
"lastName": "Wayne"
}
}
""".data(using: .utf8)!
struct Student: Decodable {
let firstName: String
let lastName: String
}
struct DecodedArray: Decodable {
var array: [Student]
// Define DynamicCodingKeys type needed for creating
// decoding container from JSONDecoder
private struct DynamicCodingKeys: CodingKey {
// Use for string-keyed dictionary
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
// Use for integer-keyed dictionary
var intValue: Int?
init?(intValue: Int) {
// We are not using this, thus just return nil
return nil
}
}
init(from decoder: Decoder) throws {
// 1
// Create a decoding container using DynamicCodingKeys
// The container will contain all the JSON first level key
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
var tempArray = [Student]()
// 2
// Loop through each key (student ID) in container
for key in container.allKeys {
// Decode Student using key & keep decoded Student object in tempArray
let decodedObject = try container.decode(Student.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
tempArray.append(decodedObject)
}
// 3
// Finish decoding all Student objects. Thus assign tempArray to array.
array = tempArray
}
}
let decodedResult = try! JSONDecoder().decode(DecodedArray.self, from: jsonData)
Therefore if I'm not changing the properties to a custom name if there no difference between the above and below implementation?
Yes, but there's a bit of misunderstanding here. The two implementations you have are literally identical because in the second one the CodingKeys enum would never be used. To be used, the enum must be nested within the Decodable conforming type (Person in this case):
struct Person: Decodable {
let firstName: String
let lastName: String
let age: Int
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case age
}
}
There is in practice no difference between this implementation and the ones you provided.
Further is there other cases where you want to use CodingKeys?
CodingKeys are not used solely by Decodable, they are also used by Encodable. When using Encodable, a reason to use CodingKeys is to specify only a subset of the instances fields should be serialized.

Decode a Swift type that is a wrapped Codable type with an extra Codable property

I've got a Codable type, let's say Car, that is defined as:
struct Car: Codable {
let age: Int
let color: String
}
I can encode/decode this just fine.
With my persistence system, when an object is stored it gets assigned an _id property, which is a String, e.g. 5cae04b533376609456d40ed.
As such, when I read the Data from the persistent store and then try to decode it there are extra bytes in there that represent the _id property and its associated String value.
I'm not in control of the various types that can be encoded and stored in the store. The only restriction on them is that they are Codable.
What I want to be able to do is decode the Data that I get when reading from the store (with the _id stuff included) into a type that is something like Wrapped<T: Codable>, which would be defined as something like (in the simplest form):
struct Wrapped<T: Codable> {
let _id: String
let value: T
}
However, I'm not sure to go about this.
One attempt I made was to to define a custom decode function but that didn't get very far as I can't seem to access the T type's CodingKeys, which makes things, as far as I can tell, impossible with that approach.
Maybe there's another approach that would make things work as I'd like?
You can write a custom decode function for your Wrapped type that parses out the _id and then passes the decoder along to the wrapped type so it can decode it's own properties:
struct Wrapped<T: Codable>: Decodable {
let _id: String
let value: T
private enum CodingKeys: String, CodingKey {
case _id
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_id = try container.decode(String.self, forKey: ._id)
value = try T(from: decoder)
}
}
You can simply declare that the _id property shouldn't decoded by defining your custom CodingKeys and omitting _id from there. You also need to assign a default value to the non-decoded properties (_id in your case) if you want to use the automatically synthetised initializer.
For a concrete type:
struct Car: Codable {
let age: Int
let color: String
let _id:Int = 0
enum CodingKeys: String, CodingKey {
case age, color
}
}
You can achieve this for all your persisted types.
If you don't want to create the CodingKeys enum for all persisted types, you could follow the generic wrapper type approach you started, but you'll need to create custom init(from:) and encode(to:) methods.
struct Persisted<T: Codable>: Codable {
let _id:Int = 0
let value:T
init(from decoder:Decoder) throws {
value = try decoder.singleValueContainer().decode(T.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}

Using Swift 4 Codable Protocol with Unknown Dictionary Keys

I am working with NASA's Near Earth Object Web Service to retrieve data to be displayed in an application. I understand how to use Swift 4's Codable protocol, but I do not understand how to map part of the response.
Using Paw, I inspected the response from the API:
As you can see, the near_earth_objects structure is a Dictionary, and the keys are dates. The issue is that the URL parameters are dates, so these date structures will change, depending on the day of the request. Therefore, I do not know how I can create properties to be automatically mapped when using the Codable protocol.
The data that I am trying to get to inside of these structures are Arrays that contain Dictionarys:
How can I have my model object conform to the Codable protocol and map these structures when the dates will change as the dates of the requests change?
You don't need to know the keys of the Dictionary compile time if you don't mind keeping a Dictionary after decoding.
You just need to specify the property with type Dictionary<String:YourCustomDecodableType>. The keys will be dates corresponding to observation and the value will an array of all objects with your custom type.
struct NearEarthObject: Codable {
let referenceID:String
let name:String
let imageURL:URL
private enum CodingKeys: String, CodingKey {
case referenceID = "neo_reference_id"
case name
case imageURL = "nasa_jpl_url"
}
}
struct NEOApiResponse: Codable {
let nearEarthObjects: [String:[NearEarthObject]]
private enum CodingKeys: String,CodingKey {
case nearEarthObjects = "near_earth_objects"
}
}
do {
let decodedResponse = try JSONDecoder().decode(NEOApiResponse.self, from: data)
} catch {
print(error)
}
As you said, near_earth_objects is a Dictionary, but keys are not Dates, keys are Strings, and values are arrays of the known structures. So the above code will work:
...
let nearEarthObjects: [String: [IndexObject]]
...
enum CodingKey: String, CodingKeys {
case nearEarthObjects = "near_earth_objects"
}
struct IndexObject: Decodable {
...
let name: String
...
}

How can I decode when I don't know the type, with class inheritance?

I have a base class Action, which is an Operation. It has a bunch of crufty Operation stuff in it (KVO and all that). The base class itself doesn't actually need to encode/decode anything.
class Action : Operation, Codable {
var _executing = false
...
}
I have a bunch of Action sub-classes, like DropboxUploadAction, which are directly instantiated with an Input struct they define:
let actionInput = DropboxUploadAction.Input.init(...)
ActionManager.shared.run(DropboxUploadAction.init(actionInput, data: binaryData), completionBlock: nil)
Here's what the subclasses look like:
class DropboxUploadAction : Action {
struct Input : Codable {
var guid: String
var eventName: String
var fileURL: URL?
var filenameOnDropbox: String
var share: Bool
}
struct Output : Codable {
var sharedFileLink: String?
var dropboxPath: String?
}
var input: Input
var output: Output
...
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
input = try values.decode(Input.self, forKey: .input)
output = try values.decode(Output.self, forKey: .output)
let superDecoder = try values.superDecoder()
try super.init(from: superDecoder)
}
fileprivate enum CodingKeys: String, CodingKey {
case input
case output
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(input, forKey: .input)
try container.encode(output, forKey: .output)
try super.encode(to: container.superEncoder())
}
}
When some situations occur such as a loss of internet connectivity, these classes need to be serialized to disk for later. That's fine, because at the time I have references to them and can encode them with JSONEncoder().encode(action), no problem.
But later when I want to deserialize them, I need to specify the type of the class and I don't know what it is. I have some data and I know it can be decoded to a class that inherits from Action, but I don't know which subclass it is. I'm loathe to encode that in the filename. Is there some way to decode it as the base class Action, then in the decode() method of Action, somehow detect the proper class and redirect?
In the past I've used NSKeyedUnarchiver.setClass() to handle this. But I don't know how to do that with Swift 4's Codable, and I understand that NSCoding is deprecated now so I shouldn't use NSKeyedUnarchiver anymore...
If it helps: I have a struct Types : OptionSet, Codable which each subclass returns, so I don't have to use the name of the class as its identity.
Thanks for any help!
Uhhh NSCoding isn't deprecated. We still use it when instantiating UIViewControllers from storyboard via init(coder:).
Also, if you still don't want to use NSCoding, you can just store the Input, Output and Types to a struct and serialize that to disk instead.
struct SerializedAction {
let input: Input
let output: Output
let type: Type
}
When needed, you can decode that and decide the correct Action to initialize with your input/output via the type property.
class DropboxAction: Action {
...
init(input: Input, output: Output) {
...
}
}
You don't necessarily need to encode the entire Action object.