"No value associated with key CodingKeys" error always thrown [duplicate] - ios

I try to use Swift 4.1's new feature to convert snake-case to camelCase during JSON decoding.
Here is the example:
struct StudentInfo: Decodable {
internal let studentID: String
internal let name: String
internal let testScore: String
private enum CodingKeys: String, CodingKey {
case studentID = "student_id"
case name
case testScore
}
}
let jsonString = """
{"student_id":"123","name":"Apple Bay Street","test_score":"94608"}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decoded = try decoder.decode(StudentInfo.self, from: Data(jsonString.utf8))
print(decoded)
} catch {
print(error)
}
I need provide custom CodingKeys since the convertFromSnakeCase strategy can't infer capitalization for acronyms or initialisms (such as studentID) but I expect the convertFromSnakeCase strategy will still work for testScore. However, the decoder throws error ("No value associated with key CodingKeys") and it seems that I can't use convertFromSnakeCase strategy and custom CodingKeys at the same time. Am I missing something?

The key strategies for JSONDecoder (and JSONEncoder) are applied to all keys in the payload – including those that you provide a custom coding key for. When decoding, the JSON key will first be mapped using the given key strategy, and then the decoder will consult the CodingKeys for the given type being decoded.
In your case, the student_id key in your JSON will be mapped to studentId by .convertFromSnakeCase. The exact algorithm for the transformation is given in the documentation:
Capitalize each word that follows an underscore.
Remove all underscores that aren't at the very start or end of the string.
Combine the words into a single string.
The following examples show the result of applying this strategy:
fee_fi_fo_fum
Converts to: feeFiFoFum
feeFiFoFum
Converts to: feeFiFoFum
base_uri
Converts to: baseUri
You therefore need to update your CodingKeys to match this:
internal struct StudentInfo: Decodable, Equatable {
internal let studentID: String
internal let name: String
internal let testScore: String
private enum CodingKeys: String, CodingKey {
case studentID = "studentId"
case name
case testScore
}
}

Related

Why does defining CodingKeys for SOME properties require an initializer?

Compiles fine with no problems:
class User: Codable {
let name: String
let email: String
}
However, if we have a property not represented by a CodingKey, it demands an initializer:
class User: Codable { // Class 'User' has no initializers
let name: String
let email: String
private enum CodingKeys: String, CodingKey {
case name = "username"
}
}
Why does it decide that a lack of synthesized initializers are the problem, instead of having a warning or error requiring a CodingKey? Why does this break conformance to Decodable?
Edit: Since some people seem to be confused, I'm not asking how to resolve the error. That is obvious. I'm asking what is happening in the Codable protocol when you don't specify a CodingKey for a required property.
A class with at least one property that doesn't have a default value needs an initializer in order to initialize that property.
class User {
let name: String
let email: String
}
Class 'User' has no initializers
When you conform to Codable, or more specifically Decodable as you well noted, the class gets a synthesized initializer, namely User.init(from: Decoder), which fixes above issue. This synthesized initializer will use coding keys named after the properties.
class User: Decodable { ... }
As soon as you define an enum within User called CodingKeys (the exact name is important), the synthesis of the Decodable conformance will require that there is a key for each property. In other words, as soon as one key deviates from the property name, you have to define a coding key for each property.
The following does that and compiles:
class User: Decodable {
let name: String
let email: String
private enum CodingKeys: String, CodingKey {
case name = "username"
case email
}
}
If not specifying the email key, the compiler will not know how to set that property, only the developer would know how. Hence, the compiler will not synthesize it for you and you have to.
There might be a natural default as shown here:
class User: Decodable {
let name: String
let email: String
enum CodingKeys: String, CodingKey {
case name = "username"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
// Default email:
email = "\(name)#example.com"
}
}
Xcode is throwing the first in a line of errors caused by the second example. If you added an initializer it would next probably tell you about something related to your lack of codingkey for the name property. However, when you don't define CodingKeys, it does is able to create CodingKeys as well as the default initializer for you.
Even though you're not going to need a CodingKey for email, you still have to declare its case, without any value as in:
private enum CodingKeys: String, CodingKey {
case name = "username"
case email
}

Trying to parse a string value from an API a JSON object into a struct as an Int

I am new to Swift and I have data I'd like to call from an API through JSON. For some strange reason some of the names are written with whitespaces making it difficult for me to declare variables that'll show the integer type value they hold. Here is a screenshot . I am trying to parse the Int value attached to the type "Anti-social behaviour" but don't seem to know how to declare it appropriately.
You can define your own CodingKeys inside the file. Note that the name should be CodingKeys.
struct Types: Codable {
let buglary: Int
let shoplifting: Int
let drugs: Int
let robbery: Int
let antiSocialBehavior: Int
// Other properties
enum CodingKeys: String, CodingKey {
case buglary = "Buglary"
case shoplifting = "Shoplifting"
case drugs = "Drugs"
case robbery = "Robbery"
case antiSocialBehavior = "Anti-social behavior"
// other coding keys
}
}
Also, note that properties in Swift are always camelcased and not capitalized. So, I also changed the name of your properties. Check the enum inside the struct which actually defines the mapping between property name and their encoding / decoding keys.

How to use nested json with structs in swift

I have an API and I need to call, to get a list of holidays with some additional info along with it. The link of my API - http://mahindralylf.com/apiv1/getholidays
The structure I created using the website app.quicktype.io
struct Holiday: Codable {
let responseCode, responseMsg: String
let holidayCount: Int
let holidays: [HolidayElement]
enum CodingKeys: String, CodingKey {
case responseCode = "response_code"
case responseMsg = "response_msg"
case holidayCount = "holiday_count"
case holidays
}
}
struct HolidayElement: Codable {
let month: String
let image: String
let details: [Detail]
}
struct Detail: Codable {
let title, date, day: String
let color: Color
}
enum Color: String, Codable {
case b297Fe = "#B297FE"
case e73838 = "#E73838"
case the0D8464 = "#0D8464"
}
I can get to the "Holiday" object, print it, display my tableViewCells with a colour for the "holidayCount". What I want to do is, without using the normal json parsing and making my own arrays and dicts, to access the "Detail" for each "holidays".
tl;dr - I need to know how to access Detail for the holidays element
Thanks!!
Your data's coming back with an array of HolidayElements and each HolidayElement has an array of Details.
So for each HolidayElement, you'd want to get access to the details array. You'd do that like so:
let jsonResponse = try JSONDecoder().decode(Holiday.self, from: responseData)
print(jsonResponse.holidays[0].details)
Here's a repo to play around with.
Additionally, your coding keys are just converting from snake_case, so you don't really need them for that endpoint. Instead, you can just tell the decoder to convertFromSnakeCase
You can ditch the coding keys in this case and just decode as follows:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let jsonResponse = try decoder.decode(Holiday.self, from: responseData)
print(jsonResponse.holidays[0].details)

The `convertFromSnakeCase` strategy doesn't work with custom `CodingKeys` in Swift

I try to use Swift 4.1's new feature to convert snake-case to camelCase during JSON decoding.
Here is the example:
struct StudentInfo: Decodable {
internal let studentID: String
internal let name: String
internal let testScore: String
private enum CodingKeys: String, CodingKey {
case studentID = "student_id"
case name
case testScore
}
}
let jsonString = """
{"student_id":"123","name":"Apple Bay Street","test_score":"94608"}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decoded = try decoder.decode(StudentInfo.self, from: Data(jsonString.utf8))
print(decoded)
} catch {
print(error)
}
I need provide custom CodingKeys since the convertFromSnakeCase strategy can't infer capitalization for acronyms or initialisms (such as studentID) but I expect the convertFromSnakeCase strategy will still work for testScore. However, the decoder throws error ("No value associated with key CodingKeys") and it seems that I can't use convertFromSnakeCase strategy and custom CodingKeys at the same time. Am I missing something?
The key strategies for JSONDecoder (and JSONEncoder) are applied to all keys in the payload – including those that you provide a custom coding key for. When decoding, the JSON key will first be mapped using the given key strategy, and then the decoder will consult the CodingKeys for the given type being decoded.
In your case, the student_id key in your JSON will be mapped to studentId by .convertFromSnakeCase. The exact algorithm for the transformation is given in the documentation:
Capitalize each word that follows an underscore.
Remove all underscores that aren't at the very start or end of the string.
Combine the words into a single string.
The following examples show the result of applying this strategy:
fee_fi_fo_fum
Converts to: feeFiFoFum
feeFiFoFum
Converts to: feeFiFoFum
base_uri
Converts to: baseUri
You therefore need to update your CodingKeys to match this:
internal struct StudentInfo: Decodable, Equatable {
internal let studentID: String
internal let name: String
internal let testScore: String
private enum CodingKeys: String, CodingKey {
case studentID = "studentId"
case name
case testScore
}
}

Get specific CodingKey for KeyPath

Consider the following object:
struct User: Codable {
let id: Int
let email: String
let name: String
}
Is it posible to get a specific CodingKey for a given KeyPath?
let key = \User.name.codingKey // would be equal to string: "name"
Using Swift 4, I don't think you can automatically retrieve a CodingKey from the corresponding KeyPath object but you can always hack your way around it ;)
For instance, in the same User type Swift source file, add the following extension:
fileprivate extension User {
static func codingKey(for keyPath: PartialKeyPath<User>) -> CodingKey {
switch keyPath {
case \User.id: return CodingKeys.id
case \User.email: return CodingKeys.email
case \User.name: return CodingKeys.name
default: fatalError("Unexpected User key path: \(keyPath)")
}
}
}
then implement the desired codingKey API in the constrained KeyPath superclass:
extension PartialKeyPath where Root == User {
var codingKey: CodingKey {
return User.codingKey(for: self)
}
}
Finally, the usage follows closely your code:
let name: CodingKey = (\User.name).codingKey
print("\(name)") // prints "name"
This may be a somewhat tedious and error prone solution but, if you only need this capability for handful of types, it's perfectly doable in my opinion ;)
Caveats. This hack, of course, won't work for externally defined types given CodingKeys enum private visibility. (For instance, for all Codable types defined by the Swift Standard Library.)
I don't think you can convert property directly to the string but you can achieve similar thing using reflection, but you have to create an instance of the struct:
let user = User(id: 1, email: "sample#email.com", name: "just_name")
Mirror(reflecting: user).children.first?.label

Resources