Override only few JSON keys using 'CodingKeys' enum - swift

I'm parsing JSON data using Codables. Problem is that my few coding keys are different from variable names. For that i used CodingKeys enum which is pretty much straight forward, however i have to write all keys then, i don't want that. I only want to override few keys not all.
here's the JSON
{
"string_one" = "some string",
"string_two" = "some string",
"string_three_%s" = "some string",
}
class Strings: Codable{
var string_one: String?
var string_two: String?
var string_three: String?
enum CodingKeys: String, CodingKey {
case string_three = "string_three_%s"
}
}
More Explanation
i know that adding case string_one, string_two will work but let's say i have 1000 strings and want to override just one, i have to write 999 cases without any reason. It does not look like a sensible thing to me(to writing 999 cases without any reason)

As of now this is not possible. You have to specify all the cases in CodingKeys.
Just that for others you don't need to specify the rawValue explicitly since the enum CodingKeys has raw type as String and will pick the case name as the default rawValue, i.e.
enum CodingKeys: String, CodingKey {
case string_three = "string_three_%s"
case string_one, string_two
}

Check this proper form of modal class.
Hope you get your solution
struct Welcome: Codable {
let stringOne, stringTwo, stringThreeS: String?
enum CodingKeys: String, CodingKey {
case stringOne = "string_one"
case stringTwo = "string_two"
case stringThreeS = "string_three_%s"
}
}

You have to set All coding keys in order to work as of now.
The class will only Encode/Decode, the keys you described in enum CodingKeys.
If you have larger number of strings, I'd suggest you to communicate with Server team for create an Array of string([String]) instead of this pattern.
Update: As you have high number of values, you should have gone for [String]

The shorter answer is that you cannot do it. This can be only done by putting all cases as below :-
enum CodingKeys: String, CodingKey {
case string_one, string_two
case string_three = "string_three_%s"
}
The keys string_one and string_two will be picked as it is

Just a suggestion (not tested) but could you use a base class (which extends Codable) with the fields you don't want to override, then use a subclass which just has the names you need to override? I.e. does CodingKeys require you define values for inherited fields?

Related

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.

Lost my conformance to Codable after adding an enum

So I have a struct that is Codable ( or was )
struct Person {
let name: String //<-- Fine
let age: Int //<-- Fine
let location: String //<-- Fine
let character: Character. //<-- Here comes the error (Type Person does not conform to Decodable)
}
enum Character {
case nice, rude
}
Now I understand the error. Every type except the enum is Codable so I'm good up until that point. Now as a way to solve this.. I was pretty sure this would work ..
extension Person {
private enum CodingKeys: CodingKey { case name, age, location }
}
But even after telling Swift the properties I want to be decoded I still get this error? Puzzled.
Since you excluded character from the keys to decode, you need to give character an initial value. After all, all properties need to be initialised to some value during initialisation.
The easiest way to do this is to give the initial value of nil:
let character: Character? = nil
Of course, you can also do:
let character: Character = .rude
Also note that you can make the enum Codable, simply by giving it a raw value type:
enum Character: Int, Codable {
// now nice will be encoded as 0, and rude will be encoded as 1
case nice, rude
}

How to get all values from nested enums?

Working with my key/value Strings file, I'm looking for a simpler way of pulling out values by passing an enum into a function, which does the lookup and returns that value.
This Strings file can be updated over the network, so my app ships with a Default version. Currently, to get those defaults into memory I iterate over my enums cases and add the strings to a dictionary.
It looks like this:
enum StringKeys: String, CaseIterable {
case string1
case string2
case string3
}
func setupDefaults() {
for key in StringKeys.allCases {
defaults[key] = stringFile.value(forKey: key)
}
}
func getValue(for: StringKeys) -> String {
// stuff here...
}
This has all been fine for a while now, however this enum is getting quite large and so autocomplete is less helpful.
What I would like to have is something more like this:
enum StringKeys: CaseIterable {
enum Feature1: String, CaseIterable {
case title1
case button1
}
enum Feature2: String, CaseIterable {
case title3
case toggle1
}
}
Then call my getValue function with:
titleLabel.text = Strings.getValue(for: StringKeys.Feature1.title1)
This is all totally fine too, but I can't work out how to iterate over these nested keys to build the defaults dictionary in a nice clean way.
I tried extending CaseIterable to add a little allCasesRecursive function. Which Swift wouldn't allow.
I know this can be done by adding a property inside each enum, returning allCases, and combining them together at each parent. But that's still a bit messy.
The other option is to not use the enums to set up defaults, and instead just parse the whole file and put that directly into defaults, but I'd much rather the enums decide what goes in the defaults. This also makes it easy to see if any are missing i.e, the default for that key won’t be found, and I can throw an error.
So, basically I want to turn a bunch of nested enums into a flat dictionary. Even just an array of keys would be enough to get me the rest of the way. Is it possible to recursively drill down through nested enums? Could I use Reflection to solve this problem?
I'm also open to other ideas, otherwise I'll just stick with the giant, flat enum.
I don't know if i understand your question correctly, maybe this will help
// Create a protocol
protocol StringKeyValue {}
enum StringKeys: CaseIterable {
enum Feature1: String, CaseIterable, StringKeyValue {
case title1
case button1
}
enum Feature2: String, CaseIterable, StringKeyValue {
case title3
case toggle1
}
}
func getValue(for: StringKeyValue) -> String {
return "hi there"
}
// Maybe this's what you want?
titleLabel.text = getValue(for: StringKeys.Feature1.title1)

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

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