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
}
Related
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.
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?
The swift4's Codable protocol is extremely useful. It provide default implementation functions if the conformation is rightly defined.
E.g. this is totally fine:
struct Good: Codable {
var foo: String // Non-optional
var bar: Int? // Optional
}
but this one will raise compile error with the request to create protocol conforming
struct Bad: Codable {
var foo: UIButton // Non-optional raise compile error for not conforming Codable Protocol
var bar: UIView? // optional is okay (not compile error because when decode failed, it fallback to nil)
var codable: SomeCodable // if the property is also Codable, then it's fine too!
}
So, the question is: Can I write a protocol that can require its conformance to follow itself (like properties need to conform same protocol)?
If yes, how? If no, why?
Also, I'd also like to know how defining CodingKeys inside the struct can change the encode/decode behaviour? Can I make something like that in my protocol as well?
Martin is correct you cannot make this on your own without touching the compiler.
First let's take a look at this basic example where I explain how coding keys are used.
struct CodableStruct: Codable {
let primitive: Int // No issues yet
enum CodingKeys: String, CodingKey {
case primitive
// This is the default coding key (i.e the JSON has structure ["primitive": 37]
// You can change this key to anything you need
//
// ex case primitive = "any_thing_you_want"
// JSON has to have structure ["any_thing_you_want": 37]
}
}
Changing the codingKey just changes the key the code will use when looking to "decode" that value from your JSON.
Now let's talk about the compiler. Let's say we create another struct
struct NotCodableStruct {
let number: Double
}
This struct does not conform to Codable. If we go and add this into our previous struct we have:
struct CodableStruct: Codable {
let primative: Int
let notCodable: NotCodableStruct // doesn't compile because this doesn't conform to codable
enum CodingKeys: String, CodingKey {
case primative
case notCodable
}
}
Since NotCodableStruct does not conform to Codable the compiler complains. In other words all variables in a struct or object that conforms to Codable must also conform to Codable. See the below screenshot for more information.
Of course if you make NotCodableStruct conform to Codable everyone will be happy again. Since there is no way for you to enforce the requirement that all variables conform to Codable you cannot make a similar protocol.
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)
}
}
Why am I getting a "Type 'Bookmark' does not conform to protocol 'Decodable'" error message?
class Bookmark: Codable {
weak var publication: Publication?
var indexPath: [Int]
var locationInText = 0
enum CodingKeys: String, CodingKey {
case indexPath
case locationInText
}
init(publication: Publication?, indexPath: [Int]) {
self.publication = publication
self.indexPath = indexPath
}
}
I do not wish to save the publication var since the Publication owns the Bookmark but the bookmark needs to know which Publication it belongs to. The decode init of Publication will set the bookmark reference to itself.
The compiler cannot synthesise the required init(from:) method due to the weak reference, so you need to write it yourself.
class Bookmark: Codable {
weak var publication: Publication?
var indexPath: [Int]
var locationInText = 0
private enum CodingKeys: String, CodingKey {
case indexPath
case locationInText
}
init(publication: Publication?, indexPath: [Int]) {
self.publication = publication
self.indexPath = indexPath
}
required init(from decoder:Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
indexPath = try values.decode([Int].self, forKey: .indexPath)
locationInText = try values.decode(Int.self, forKey: .locationInText)
}
}
Why am I getting a "Type 'Bookmark' does not conform to protocol 'Decodable'" error message
It's either because Publication isn't Decodable (you have not shown what it is, so it's hard to tell) or because of the weak designation on publication.
Either way, it's easy to fix: you just need to implement init(from:) to complete the implementation of Decodable; the compiler is simply telling you that this implementation cannot be synthesized.
Another reason you could get this message is if your CodingKeys enum isn't exhaustive. If you have three properties in the data type, then your CodingKeys enum needs to have three property/name cases as well.
On the hindsight, I received a similar error when trying to set Codable to my class which consisted of NSNumber type variables. See image below:
Changing NSNumber to primitive data type Int resolved the issue. See below:
I'm guessing this might be true for other datatypes that require bridging to Swift Standard Library Value Types such as NSString, NSArray and so on
In a similar scenario, I was getting the same issue because the variable name in my CodingKeys was not the same as a class variable. See below
Simply because your CodingKeys enum is not exhaustive, add publication property to the enum to achieve that.
try this:
class Bookmark: Codable {
weak var publication: Publication?
var indexPath: [Int]
var locationInText = 0
// All your properties should be included
enum CodingKeys: String, CodingKey {
case indexPath
case locationInText
case publication // this one was missing
}
}
You wont need the init method anymore as the implementation now can be synthesized.
Any class to be a codeable must have it's all property codeable.
Standard library types like String, Int, Double and Foundation types like Date, Data, and UR confirm the codeable protocol but some doesn't.
For e.g below
Note class have all properties of string which confirm codable protocol so no error:
But UIImage don't confirm codable protocol so it throw error:
You can omit a property from coding keys enum, only if it has a default value.
From apple docs
Omit properties from the CodingKeys enumeration if they won't be present when decoding instances, or if certain properties shouldn't be included in an encoded representation. A property omitted from CodingKeys needs a default value in order for its containing type to receive automatic conformance to Decodable or Codable.
In-short, while implementing Codable, all properties which are non-primitive data type (mean class type or may it can be objective-c class) must be Codable.
weak var publication: Publication?
in this case publication is of type class so Publication must have implemented Codable
Bit of a daft one but in case it helps someone else. I got this error because I put enum CodingKeys: CodingKeys instead of enum CodingKeys: CodingKey.
If you have tried all the above solutions and are still unable to fix the error then I think it could be because of the data type you are using for your data class fields.
In the question they have used 1 field weak var publication: Publication?, and if you have the same kind of class structure then maybe you should check if that Publication data class conforms to the Codable class or not.
Because it is mandatory to conform same protocol for the child class as well, as it's fields are also should be encodable and decodable.
I had a similar issue which I stumbled upon this fix to. As I am new to Swift, I am unsure as to why it works! If anyone knows I would appreciate the knowledge.
I changed this:
let id, type: Int
to this:
let id: Int
let type: Int