Can't use #available unavailable with Codable - swift

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
}

Related

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
}

Decoding/Encoding a struct with protocol type properties

I am trying to save a configuration data structure with UserDefaults, thus the data structure needs to conform to the Codable protocol. This is my data structure:
// Data structure which saves two objects, which conform to the Connection protocol
struct Configuration {
var from: Connection
var to: Connection
}
protocol Connection: Codable {
var path: String { get set }
}
// Two implementations of the Connection protocol
struct SFTPConnection: Connection, Codable {
var path: String
var user: String
var sshKey: String
}
struct FTPConnection: Connection, Codable {
var path: String
var user: String
var password: String
}
If I just add Codable to Configuration, it won't work. So I have to implement it myself.
extension Configuration: Codable {
enum CodingKeys: String, CodingKey {
case from, to
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let from = try container.decode(Connection.self, forKey: .from)
let to = try container.decode(Connection.self, forKey: .to)
self.from = from
self.to = to
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(from, forKey: .from)
try container.encode(to, forKey: .to)
}
}
For every call on decode() or encode() I get the error Protocol type 'Connection' cannot conform to 'Decodable/Encodable' because only concrete types can conform to protocols.
I can see that it is difficult for the compiler to identify, which class should be used to decode the given object. But I figured it should be easy to encode an object, since every object of type Connection implements the encode() method.
I know, that the problem lies with the protocol and that the protocol can't be used with Decodable/Encodable. How would I change the code in decode/encode, so that I can still use the protocol with the various implementations? My guess is to somehow tell decode/encode which implementation of the protocol to use. I would appreciate any elegant solutions for this problem!
It's a limitation of Swift that a protocol cannot conform to itself. Thus from and to do not conform to Codable as bizarre as that seems.
You can get around it by using generics which basically means you declare from and to as arbitrary types that conform to Codable. Here's how:
struct Configuration<F: Connection, T: Connection>: Codable {
var from: F
var to: T
}
let myFrom = SFTPConnection(path: "foo", user: "me", sshKey: "hgfnjsfdjs")
let myTo = FTPConnection(path: "foo", user: "me", password: "hgfnjsfdjs")
let example = Configuration(from: myFrom, to: myTo)
So F and T are types that conform to Connection. When you instantiate example in the last line, the compiler infers F is SFTPConnection and T is FTPConnection.
Once I added the generic parameters, Configuration was able to synthesise the conformance to Codable without the extension.
To answer Sh_kahn's point about having two generic parameters, I did this to allow from and to to be connections of different types. If you always want the two connections to be of the same type i.e. always two SFTPConnections or two FTPConnections you should declare the Configuration like this:
struct Configuration<C: Connection>: Codable {
var from: C
var to: C
}

Can I write protocol behave similar to Encodable & Decodable?

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.

How to make a struct conforms to a protocol which has a property conforms to another protocol in swift 4?

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

Codable class does not conform to protocol Decodable

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