Get coding value for a Codable value in Swift - swift

I am working on a Swift service that stores codable types in JSON. The type also defines a nested CodingKeys enum to define the property names when coded to JSON. For example, I might have a type that looks like this:
struct MyType : Codable {
let myIntProperty: Int
let myStringProperty: String
enum CodingKeys: String, CodingKey {
case myIntProperty = "int_property"
case myStringProperty = "string_property"
}
}
Now I have a function that wants to operate on one of these properties, as specified by a key path to that property. Using the type from the example, I want a function that can be called like this:
let result = myFunc(usingProperty: \MyType.myIntProperty)
My function is declared using the KeyPath type:
func myFunc<T: Codable, G>(usingProperty: KeyPath<T, G>) -> G {
...
}
In order to implement my function, I would like to somehow get access to not just the key path parameter named usingProperty, but also to the coding key that it is associated with. So when called with the key path to the myIntProperty property, I would want to get the name used when encoding it, which is int_property.
Is there any way to derive this value from the KeyPath parameter and its root type's CodingKeys enumeration?

Related

Codable default values during initialization

I am new to Swift and I am working on a feature flag concept for my project and I am stuck using codable for default flag values. Currently my code looks like this
import Foundation
class KillSwitches: Codable {
public enum CodingKeys: String, CodingKeys {
case featureOne
case featureTwo
case featureThree
}
let featureOne: Bool = true
let featureTwo: Bool = true
let featureThree: Bool = false
}
I have internal helper classes which helps with encoding and decoding of all the values from the json file and that's why its not explicitly mentioned here. Prior to this implementation I didn't have any default values and was using struct reading everything from a remote config file which was working fine. Now I am in my next step to have default values for my features if in case the remote config file is unreachable.
I was expecting that I could initialize this class so I will get an object of the class with the default just like what I was getting when I read from my remote file.
I am not able to instantiate this class without passing init(from decoder:). I even tried doing
KillSwitches.init(from: KillSwitches.self) which is not working either and I get the Type does not conform to expected type Decoder.
My Json looks like this
{
"featureOne" : false,
"featureTwo" : true,
"featureThree" : true
}
Any guidance / pointers to resolve this problem is much appreciated.
Once you conform to Encodable, it's as if your class has explicitly declared a encode(to:) method and a init(from:) initialiser.
By declaring an initialiser with arguments, you immediately lose the default (parameterless) initialiser that the compiler generates for you when all properties have a default value. This is why you can't do KillSwitches(). This is stated in the documentation:
Swift provides a default initializer for any structure or class that
provides default values for all of its properties and does not provide
at least one initializer itself. The default initializer simply
creates a new instance with all of its properties set to their default
values.
KillSwitches has a init(from:) initialiser already, so Swift doesn't provide the default initialiser.
You just have to add the parameterless initialiser in yourself:
class KillSwitches: Codable {
public enum CodingKeys: String, CodingKey {
case featureOne
case featureTwo
case featureThree
}
let featureOne: Bool = true
let featureTwo: Bool = true
let featureThree: Bool = false
init() { }
}
And then you can do:
let defaultKillSwitches = KillSwitches()
if you want the default values.

Is there a way to extract name of case of CodingKeys enum?

I'm trying to access the CodingKeys enum case's name. The only thing I can access to is its stringValue. I need to know the name because I comparing it to the variable name in the Decodable object. And sometimes the stringValue and the actual name is different.
I am aware that I could manually write a variable that would return the name of each case...but that wouldn't be scalable as it would make the models have unnecessary amount of boiler plate code.
This is basic example where the name is different from the stringValue.
I need to somehow extract the name.
#objcMembers
class User: Decodable {
dynamic var name: String = ""
enum CodingKeys: String, CodingKey {
case name = "user_name"
}
}
if you have a instance of your enum, you can convert it's name to string like this:
let myEnum : CodingKeys = .name
let stringRepresentation = "\(myEnum.self)"
print(stringRepresentation) //this will print "name" on the console

Is it possible to access a struct member using a variable in Swift 4?

I've got a list of instances of the following struct called payment_list:
struct Payment: Codable {
let id: Int
let payment_hash: String
let destination: String
let msatoshi: Int
let timestamp: Int
let created_at: Int
let status: String
enum PaymentKeys: String, CodingKey {
case payments
}
}
I can access the members of each struct instance in the list in the following manner:
print(payment_list[0].id) // 1
print(payment_list[0].timestamp) // 1517083775
Is it possible to access those struct instance members using a variable to determine which member is being accessed? Something along the lines of:
var column = "id"
print(payment_list[0][column]) // 1
I've read about people using NSObject and value(forKey key: String), but this is a struct so it can't inherit from NSObject. If it was an object I think it would look something like this:
var column = "id"
print(payment_list[0].value(forKey key: column)) // 1
Is this type of thing possible with struct?
Swift 4 added support for key paths, which allow to do something similar to what you want:
let keyPath: KeyPath<Payment, Int> = \Payment.id
var payment = Payment(id: 42)
let value = payment[keyPath: keyPath] // returns 42
A key path has two generic parameters: first the type it operates on (in your example Payment), and the type of the property it accesses (Int). Note that you'll have to specify the key path for every property you want to access.

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
...
}

Enum structure creation

I'm working on a model definition with Swift 3.
I have two simple enum :
enum CompanyField: String {
case id
case name
}
enum UserField: String {
case email
case id
case id_company
}
When using collections I would to like to conform to a generic type. Something like Field e.g:
let fields: [Field : String] = [UserField.id : "1", CompanyField.name : "A name"]
or
let fields: [Field] = [UserField.id, CompanyField.name]
I was trying to create a Field protocol but without success. (e.g: enum UserField: Field {})
Is there any solution to have my two enum conforming to one single custom type ?
This can give you a flexible way to express you enums conform a custom domain protocol and still be used as dictionary and array elements. but again, this avoid to mix different enum types as dict key:
func map<K,V>(forFields fields: (K,V)...) -> [K:V] where K : Hashable{
var dict = [K:V]()
fields.forEach { (key, value) in
dict[key as K] = value
}
return dict
}
map(forFields: (UserField.email, "email"), (UserField.id,"id"))