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
}
Related
So I am getting a warning (and I can't dismiss it because it won't run the JSON if I do)
The warning is for the following
struct Station: Codable, Identifiable {
let id = UUID() // WARNING: Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten
var name : String
var imageurl : String
var listenlive : String
}
The problem is the following
I can't make it UUID as I get the following error
Type 'Station' does not conform to protocol 'Decodable'
// STATION
struct Sdata: Codable {
var data: [Station]
}
struct Station: Codable, Identifiable {
let id = UUID
var name : String
var imageurl : String
var listenlive : String
}
Also technically the let id is wrong because the JSON has it as _id
"_id":"5f52ed668b964609655b48d1"
So How do I make the warning go away while conforming to the correct value type?
In this case, it's not a matter of "getting rid of the warning". What you are doing seems fundamentally wrong.
The way you declared struct Station, every time a new struct is created, its "id" field is set to a new UUID, that is every struct Station has a different id field, that cannot be changed afterwards.
So if we ignore Codeable for the moment, how would you write such a struct to a file and read it back later, getting the same id? Answer: You can't read it back. At some point you create a struct Station with a new, different UUID, and there is no way to store the one that you wrote to a file. So you have a problem here.
So the problem is quite obvious: If you have a "let" property with a non-constant value, Codeable would have to write it, but Decodable cannot read it, so you have a problem. Make "id" a var, possibly with a constructor that sets it to a new UUID, but it must be changeable.
You need to change it to id:String and use enum CodingKeys for the custom name _id
struct Station: Codable , Identifiable {
var id,name,imageurl,listenlive:String
private enum CodingKeys : String, CodingKey {
case id = "_id", name , imageurl , listenlive
}
}
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
}
}
I have a service that returns this JSON structure "actual-price": {,
I want to know if it is possible to create a variable like JS with Codable on swift.
PS: I can't change the JSON since the service is not mine
You will need to use a CodingKeys enumeration to map the jSON properties to valid Swift properties. Note that once you introduce a CodingKeys enumeration it must contain all of the properties you wish to map, not just the properties where you want to change the name.
Something like
struct MyStruct: Codable {
var actualPrice: Double
var quantity: Int
enum CodingKeys: String, CodingKey {
case actualPrice = "actual-price"
case quantity = "quantity"
}
}
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
}
}
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