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.
Related
I would like to apply the available attribute with the renamed and unavailable arguments to a property of struct that conforms to Codable , as shown below:
struct SampleData: Codable {
#available(*, unavailable, renamed: "newProperty")
let oldProperty: String
let newProperty: String
}
But when I tried to build this code , I got a compile error like this:
note: 'oldProperty' has been explicitly marked unavailable here
If a struct does not conform to Codable, it works well.
Does anyone know how to resolve this problem?
And if it is impossible to resolve this, I'd appreciate it if you could tell me why.
Thanks in advance.
This is because the synthesised Codable conformance is trying to decode/encode oldProperty as well. It can't not do that, because all stored properties has to be initialised, even if they are unavailable.
It will work if you initialise oldProperty to some value, and add a CodingKey enum to tell the automatically synthesised conformance to only encode/decode newProperty:
struct SampleData: Codable {
#available(*, unavailable, renamed: "newProperty")
let oldProperty: String = ""
let newProperty: String
enum CodingKeys: CodingKey {
case newProperty
}
}
Actually, depending on the situation, you might be able to convert oldProperty to a computed property, in which case you don't need the coding keys.
struct SampleData: Codable {
#available(*, unavailable, renamed: "newProperty")
var oldProperty: String { "" }
let newProperty: String
}
I have a root object that consists mainly of an array of tuples and then a bunch of state enums. The tuples are described this way:
typealias Program = [(Number, Statement)]
The root object is currently NSCoding, but I'd like to add Codable to make the switch to JSON. In most cases, making the various parts become Codable requires nothing more than adding , Codable to their declarations, but that's not working for the tuples. I think this is just a syntax issue, but neither of these work:
typealias Program, Codable = [(Number, Statement)]
typealias Program = [(Number, Statement)], Codable
Is this possible? The horrible documentation only mentions typealias when describing the protocol itself, and perhaps my Google-fu is failing, but I'm not seeing cogent references here on SO.
UPDATE: root object(s):
typealias Program = [(Number, Statement)]
public enum InterpreterState: Int, Codable {
case Idle
case ReadingStatement // while parsing code
case Running // while running it
case ReadingInput // while waiting on user input
}
public final class Interpreter: NSObject, NSCoding {
/// Array of program lines
var program: Program = Array()
/// State
var state: InterpreterState = .Idle
etc.
Tuples are not Codable. If you think about it, what should the keys for (Number, Statement) be? (I might have made it sound like named tuples are codable, but they aren't either)
Create your own struct:
struct ProgramLine: Codable {
let lineNumber: Number // assuming these types are codable
let statement: Statement
}
And then your type alias will be codable too (without you doing anything)!
typealias Program = [ProgramLine]
I was going to reflect some JSON data from web service into swift struct. So I created a protocol which conforms to decodable protocol and planed to create some structs to conform it. This is the protocol I had created:
protocol XFNovelApiResponse: Decodable {
var data: Decodable {get}
var error: NovelApiError {get}
}
struct NovelApiError: Decodable {
let msg: String
let errorCode: String
}
It was compiled. But when I started to write my struct I got an error. The struct's code is here:
struct XFNovelGetNovelsApiResponse: XFNovelApiResponse {
let data: NovelsData
let error: NovelApiError
struct NovelsData: Decodable {
}
}
The error says type 'XFNovelGetNovelsApiResponse' does not conform to protocol 'XFNovelApiResponse'. I know the 'data' property should be implemented in wrong way. How can I fix it? Thanks.
You are asking to describe the kind of type that data can hold, rather than the actual type. That means it needs to be an associatedtype:
protocol XFNovelApiResponse: Decodable {
associatedtype DataType: Decodable
var data: DataType {get}
var error: NovelApiError {get}
}
Note that protocols with associated types can generate a lot of complexity, so you should carefully consider if this protocol is really necessary, or if XFNovelApiResponse could, for example, be generic instead. It depends on what other types implement this protocol.
For example, another implementation of a similar set of data structures without protocols would be:
struct XFNovelApiResponse<DataType: Decodable>: Decodable {
var data: DataType
var error: NovelApiError
}
struct NovelsData: Decodable {
}
struct NovelApiError: Decodable {
let msg: String
let errorCode: String
}
let novels = XFNovelApiResponse(data: NovelsData(),
error: NovelApiError(msg: "", errorCode: ""))
Alternately, you can implement this with classes and subclasses, which allow inheritance. Structs do not inherit from protocols, they conform to protocols. If you really mean inheritance, classes are the right tool. (But I expect generics are the better solution here.)
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
I was playing with Swift 4 and Codable a little bit and got stucked with some scenario having nested protocols which all conform to Codable.
Simplified example looks like this:
protocol CodableSomething: Codable {}
protocol CodableAnotherThing: Codable {
var something: CodableSomething { get }
}
struct Model: CodableAnotherThing {
var something: CodableSomething
}
This code is making a build errors with Xcode 9 Beta 5:
Type 'Model' does not conform to protocol 'Decodable'
Type 'Model' does not conform to protocol 'Encodable'
Now, I wasn't expecting these errors as I understood that conformance to these protocols will be auto-generated by the compiler, when in fact, I couldn't even implement this conformance manually without having build errors. I've also tried several different approaches to solve this kind of a nested model structure with using Codable but I just couldn't make it work.
My question: Is this a compiler bug (it's still beta) or I'm doing something wrong?
If you switch protocol
CodableSomething
To a struct you would have no errors,
take it further and read more about Codable
what are the types that a Codable can work on and why ?
up there you are basically saying this to xCode
struct foo: Codable {
var ok: Codable
}
That is not right take a deep look at it,
Codable is a Typealias
you need to conform to to use its subs such as .Decode() , .Encode()
those methods works with values not abstraction types
so giving a Codable Type to a Variable thats not going to work out.
because Codable is a typealias that indicates
Decodable & Encodable
/// A type that can convert itself into and out of an external representation.
public typealias Codable = Decodable & Encodable
and both of Decodable and Encodable are Protocols that make sure those values are encodable and decodable.
so Codable is an abstraction it can't Decode or Encode Variables of it self Type
but can encode and decode Types that are confirmed to it.