Why not conform to protocol encodable decodable? - swift

I have the following structure of code. If I omit the codingkey part the code is running. I implemented StringConverter to convert a string to Int (from bySundell Swift Side)
struct Video: Codable {
var title: String
var description: String
var url: URL
var thumbnailImageURL: URL
var numberOfLikes: Int {
get { return likes.value }
}
private var likes: StringBacked<Int>
enum CodingKeys: String, CodingKey{
case title = "xxx"
case description = "jjjj"
case url = "url"
case thumbnailImageURL = "jjjjjjjj"
case numberofLikes = "jjjjjkkkk"
}
}
// here the code for converting the likes
protocol StringRepresentable: CustomStringConvertible {
init?(_ string: String)
}
extension Int: StringRepresentable {}
struct StringBacked<Value: StringRepresentable>: Codable {
var value: Value
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
let stringToConvert = string.split(separator: "/").last!.description
guard let value = Value(stringToConvert) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: """
Failed to convert an instance of \(Value.self) from "\(string)"
"""
)
}
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value.description)
}}
As i said, if I omit the Codingkeys part it does me show no error. I would simply create a struct, where I get a string from a Rest API and I want to convert in an Int for my database using Codable.
Thanks
Arnold

When you define CodingKeys, you need to provide key for each non-optional/non-initialized property so that compiler know how to initialize while decoding. Applying this to Video, it will look like this,
struct Video: Codable {
var title: String
var description: String
var url: URL
var thumbnailImageURL: URL
var numberOfLikes: Int {
return likes.value
}
private var likes: StringBacked<Int>
enum CodingKeys: String, CodingKey{
case title = "xxx"
case description = "jjjj"
case url = "url"
case thumbnailImageURL = "jjjjjjjj"
case likes = "jjjjjkkkk"
}
}
If you see closely, this property private var likes: StringBacked<Int> was not provided any CodingKey in the enum so compiler was complaining. I updated the enum with this case case likes = "jjjjjkkkk" and removed case numberofLikes = "jjjjjkkkk" because numberofLikes is a read only computed property that doesn't need any parsing.

Related

Using CodingKeys with custom protocol

I have the following Codable protocol containing a variable which I would like to exclude from the codable ones.
Problem is that I can't use the CodingKeys enum made for that within my own protocol: Type 'CodingKeys' cannot be nested in protocol 'Animal'.
protocol Animal: Codable {
var name: String { get set }
var color: String { get }
var selfiePicture: Selfie { get }
// Not possible
enum CodingKeys: String, CodingKey {
case name
case color
}
}
How could I resolve this?
EDIT with more code and more specific example
Animal is used by several structs (can't be classes):
struct Frog: Animal {
var name: String
var color: String
// extra variables on top of Animal's ones
var isPoisonous: Bool
var selfiePicture = [...]
}
It is also used as a variable array on another top-codable object:
final class Farm: Codable {
var address: String
// more variables
var animals: [Animal]
enum CodingKeys: String, CodingKey {
case address
case animals
}
convenience init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
address = try values.decode(String.self, forKey: .address)
animals = try values.decode([Animal].self, forKey: .animals) // ERROR --> Protocol 'Animal' as a type cannot conform to 'Decodable'
}
}
One way to solve this is to use composition, move the common properties to a new type and use that type in the protocol.
So let's make a type for the common properties and let that type hold the CodingKey enum
struct AnimalCommon: Codable {
var name: String
var color: String
var selfiePicture: Selfie = Selfie()
enum CodingKeys: String, CodingKey {
case name
case color
}
}
And the protocol becomes
protocol Animal: Codable {
var common: AnimalCommon { get set }
}
After that it will be quite easy to implement the actual Animal types, for example
struct Frog: Animal {
var common: AnimalCommon
var isPoisonous: Bool
}
let frog = Frog(common: AnimalCommon(name: "kermit", color: "green"), isPoisonous: false)
do {
let data = try JSONEncoder().encode(frog)
if let string = String(data: data, encoding: .utf8) { print(frog) }
} catch {
print(error)
}
You can also add an extension to the protocol with computed properties so you can access the properties directly, i.e frog.name = "Kermit"
extension Animal {
var name: String {
get {
common.name
}
set {
common.name = newValue
}
}
var color: String {
common.color
}
}
Following Protocol type cannot conform to protocol because only concrete types can conform to protocols, I had to give up on the protocol and use a struct + enum inside instead.
Even though #JoakimDanielson's answer was promising, it does not fix an error I have while trying to decode the Animal array from my Farm class: Protocol 'Animal' as a type cannot conform to 'Decodable'.
Here is how my model looks like at the end:
struct Animal: Codable {
enum Species: Codable {
case frog(FrogSpecificities)
case ...
var selfiePicture: Selfie {
switch self {
case .frog(let frogSpecificities):
return frogSpecificities.selfie
case ...
...
}
}
enum CodingKeys: String, CodingKey {
case FrogSpecificities
case ...
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let frogSpecificities = try? values.decode(FrogSpecificities.self, forKey: .frogSpecificities) {
self = .frog(frogSpecificities)
} else if ...
...
} else {
// throw an error here if no case was decodable
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .frog(let frogSpecificities):
try container.encode(frogSpecificities, forKey: .frogSpecificities)
case ...:
...
}
}
}
var name: String
let color: String
var species: Species
enum CodingKeys: String, CodingKey {
case name
case color
case specificities
}
}
struct FrogSpecificities: Codable {
let isPoisonous: Bool
let selfie = Selfie()
enum CodingKeys: String, CodingKey {
case isPoisonous
}
}
final class Farm: Codable {
var address: String
var animals: [Animal]
enum CodingKeys: String, CodingKey {
case address
case animals
}
convenience init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
address = try values.decode(String.self, forKey: .address)
animals = try values.decode([Animal].self, forKey: .animals) // Works fine
}
}
My Farm object can now contains an Animal arrays with specific codable struct for each one of them. It can also contains variables which are not codable.
I can access each specificities of my animals like this:
if let firstAnimel = MyFarm.animals.firstObject,
case .frog(let frogSpecificities) = firstAnimal.species {
print(frogSpecificities.isPoisonous)
}

How to manually decode an array of items conforming to a custom protocol?

I am trying to decode an array of items logicBlocks that conform to a custom protocol LogicBlock.
struct MyModel: Codable {
var var1: String
var var2: Int
var logicBlocks: [LogicBlock]
}
I've looked at this question which asks how to decode an array of items, however I have a protocol which contains an enum like this:
enum LogicBlockTypeID: Int, Codable {
case a = 500
case b = 501
case c = 502
}
protocol LogicBlock: Codable {
var typeID: LogicBlockTypeID { get }
}
and structs implement LogicBlock like this:
struct Struct1: LogicBlock {
var someVar = 32
var typeID = .a
}
struct Struct2: LogicBlock {
var someString = "hello"
var typeID = .b
}
struct Struct2: LogicBlock {
var typeID = .c
}
I have attempted to decode MyModel by using init(from decoder: Decoder)
var codeContainer = try values.nestedUnkeyedContainer(forKey: .code)
var parsedCode = [LogicBlock]()
enum codingKeys: String, CodingKey {
...
case .logicBlocks
}
init(from decoder: Decoder) {
...
var codeContainer = try values.nestedUnkeyedContainer(forKey: .code)
var parsedCode = [LogicBlock]()
while !codeContainer.isAtEnd {
let nestedDecoder = try codeContainer.superDecoder()
let block = try Program.decodeLogicBlock(from: nestedDecoder)
parsedCode.append(block)
}
}
private static func decodeLogicBlock(from decoder: Decoder) throws -> LogicBlock {
let values = try decoder.container(keyedBy: LogicBlockTypeID.self)
// SOMETHING TO DISTINGUISH WHAT TYPE WE HAVE
return ...
}
How are we able to know the type of object that we are decoding here?
You can implement a concrete type, i.e AnyLogicBlock that would expose two variables let typeID: LogicBlockTypeID and let wrapped: LogicBlock
Here is a simplified example:
enum TypeID: String, Codable {
case a, b
}
protocol MyType {
var type: TypeID { get }
}
struct MyConreteTypeA: MyType {
var type: TypeID
var someVar: Int
}
struct MyConreteTypeB: MyType {
var type: TypeID
var someString: String
}
struct AnyType: MyType, Decodable {
private enum CodingKeys: String, CodingKey {
case type, someVar, someString
}
let type: TypeID
let wrapped: MyType
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(TypeID.self, forKey: .type)
switch type {
case .a:
wrapped = MyConreteTypeA(type: type, someVar: try container.decode(Int.self, forKey: .someVar))
case .b:
wrapped = MyConreteTypeB(type: type, someString: try container.decode(String.self, forKey: .someString))
}
}
}
var json = #"""
[
{ "type": "a", "someVar": 1 },
{ "type": "b", "someString": "test" }
]
"""#
let result = try! JSONDecoder().decode([AnyType].self, from: json.data(using: .utf8)!)
for item in result {
switch item.type {
case .a:
let casted = item.wrapped as! MyConreteTypeA
print("MyConreteTypeA: \(casted)")
case .b:
let casted = item.wrapped as! MyConreteTypeB
print("MyConreteTypeB: \(casted)")
}
}
Alternatively you can implement Decodable for concrete types and delegate decoding to them after determining the expected type based on type ID property.

Exclude CodingKeys that doesn't need to be altered?

Say I have a struct User model which has many properties in it.
struct User: Codable {
let firstName: String
let lastName: String
// many more properties...
}
As you can see above it conforms to Codable. Imagine if the lastName property is should be encoded/decoded as secondName and I would like to keep it as lastName at my end, I need to add the CodingKeys to the User model.
struct User: Codable {
//...
private enum CodingKeys: String, CodingKey {
case firstName
case lastName = "secondName"
// all the other cases...
}
}
Is there any possible way to avoid including all the cases in CodingKeys that have the same value as rawValue like the firstName in the above example (Feels redundant)? I know if I avoid the cases in CodingKeys it won't be included while decoding/encoding. But, is there a way I could override this behaviour?
There is a codable way, but the benefit is questionable.
Create a generic CodingKey
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }
init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue }
}
and add a custom keyDecodingStrategy
struct User: Codable {
let firstName: String
let lastName: String
let age : Int
}
let jsonString = """
{"firstName":"John", "secondName":"Doe", "age": 30}
"""
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ keyPath -> CodingKey in
let key = keyPath.last!
return key.stringValue == "secondName" ? AnyKey(stringValue:"lastName")! : key
})
let result = try decoder.decode(User.self, from: data)
print(result)
} catch {
print(error)
}
There is not such a feature at this time. But you can take advantage of using computed properties and make the original one private.
struct User: Codable {
var firstName: String
private var secondName: String
var lastName: String {
get { secondName }
set { secondName = newValue }
}
}
So no need to manual implementing of CodingKeys at all and it acts exactly like the way you like. Take a look at their counterparts:

Swift Codable: Is it possible to encode a structure one level up from within its own "encode(to:)" function?

I'm using a protocol to encode the conforming structures:
protocol RequestParameters: Encodable {
}
extension RequestParameters {
func dataEncoding() -> Data? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return data
}
}
This works perfectly fine for encoding these kind of structures:
struct StoreRequest: RequestParameters {
var storeCode : String
var storeNumber : String
}
However, sometimes my requests require some "shared" parameters:
struct SpecialStoreRequest: RequestParameters {
var storeCode : String
var storeNumber : String
// Shared Parameters that appear in 70% of my requests
var sharedParam1 : String?
var sharedParam2 : String?
var sharedParam3 : String?
var sharedParam4 : String?
var sharedParam5 : String?
}
I could simply write these shared parameters on each of my request structures that need them, but I was wondering if it is possible to group them in another structure and somehow modify the encoding to encode them on the top level instead?
I'm thinking of something similar to this:
struct SharedParameters {
// Shared Parameters that appear in 70% of my requests
var sharedParam1: String?
var sharedParam2: String?
var sharedParam3: String?
var sharedParam4: String?
var sharedParam5: String?
enum CodingKeys: String, CodingKey {
case sharedParam1
case sharedParam2
case sharedParam3
case sharedParam4
case sharedParam5
}
}
struct SpecialStoreRequest: RequestParameters {
var storeCode : String
var storeNumber : String
var sharedParams : SharedParameters?
}
The problem with this last structure is that the resulting encoding is NOT the same as the first one because my shared parameters will be encoded INSIDE the "sharedParams" key:
{
"storeCode" : "ABC",
"storeNumber" : "123456",
"sharedParams" : {"sharedParam1" : "A","sharedParam2" : "B", ...}
}
But what I need is for them be encoded along my other existing parameters (storeCode & storeNumber in this case).
{
"storeCode" : "ABC",
"storeNumber" : "123456",
"sharedParam1" : "A",
"sharedParam2" : "B",
...
}
EDIT:
To make the question clearer, assuming it is possible, what should go here to make this structure be encoded by key-value directly on its parent?
extension SharedParameters: Encodable {
func encode(to encoder: Encoder) throws {
// What goes here? (Is it even possible?)
}
}
was wondering if it is possible to group them in another structure and somehow modify the encoding to encode them on the top level instead?
You can't change the current Encoder and how it behaves but,
You can achieve that by customizing the Encode functions,
make two containers and use the shared parameters CodingKeys to encode
parameters inside your sharedParameters variable.
Observe the code Below.
struct Others: Codable {
var sharedParam1: String
var sharedParam2: String
enum CodingKeys: String, CodingKey {
case sharedParam1
case sharedParam2
}
}
struct MyCustomReq: Codable {
var p1: String
var p2: String
var shared: Others
enum CodingKeys: String, CodingKey {
case p1
case p2
case shared
}
}
extension MyCustomReq {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(p1, forKey: .p1)
try container.encode(p2, forKey: .p2)
//Others = sharedParams container. with its CodingKeys
var container2 = encoder.container(keyedBy: Others.CodingKeys.self)
try container2.encode(shared.sharedParam1, forKey: .sharedParam1 )
try container2.encode(shared.sharedParam1, forKey: .sharedParam2)
}
}
Usage Test
var oth = Others(sharedParam1: "Shared1", sharedParam2: "Shared2")
var object = MyCustomReq.init(p1: "P1", p2: "P2", shared: oth)
let encoder = JSONEncoder()
let data = try encoder.encode(object)
print(String(data: data, encoding: .utf8)!)
OUTPUT
{
"p2":"P2",
"sharedParam1":"Shared1",
"p1":"P1",
"sharedParam2":"Shared1"
}
Now lets take it to the next step,
Create a class and custom the Encoder of the shared there and just call its function.
Observe the final result.
final class MyParamsEncoded: Codable {
var sharedParams: Others
init (sharedParam: Others) {
self.sharedParams = sharedParam
}
func encode(to encoder: Encoder) throws {
var container2 = encoder.container(keyedBy: Others.CodingKeys.self)
try container2.encode(sharedParams.sharedParam1, forKey: .sharedParam1 )
try container2.encode(sharedParams.sharedParam1, forKey: .sharedParam2)
}
}
Now after Adding this class you can just use it like this,
And it will give you the same result.
extension MyCustomReq {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(p1, forKey: .p1)
try container.encode(p2, forKey: .p2)
//Using the class wrapping, final Result of using
var cont = try MyParamsEncoded(sharedParam: shared).encode(to: encoder)
}
}

How to make the RealmSwift RealmOptional compatible with Swift Codable?

Im facing an issue where I can't make the RealmOptional compatible with swift new Codable feature with json decoder.
Cosider the following Realm object.
class School: Object, Codable {
#objc dynamic var id: Int64 = 0
#objc dynamic var name: String?
var numberOfStudents = RealmOptional<Int64>()
var classes = List<Class>()
enum CodingKeys: String, CodingKey {
case id
case name
case numberOfStudents
case classes
}
}
class Class: Object, Codable {
var name: String?
var numberOfStudents = RealmOptional<Int64>()
}
Here we can declare the class as Codable because I wrote an extension for RealmOptinal with the help of this gist. But the problem is when the decoder decodes the json.
Consider this json
let jsonData = """
[
"id": 1234,
"name": "Shreesha",
"numberOfStudents": nil,
"classes": {
"name": "Class V",
"numberOfStudents": 12
}
]
""".data(using: .utf8)!
In this json all the data are passed and this decodes perfectly with the code.
let decoder = JSONDecoder()
let decoded = try! decoder.decode(School.self, from: jsonData)
But if I remove the numberOfStudents key from the json data which supposed to be a RealmOptional object it will throw an error and it will not decode because RealmOptional is not a swift optional so the decoder thinks that there should be a key in the json data. In JSONDecoder it doesn't try to decode if the key is not there in the json and the property is declared as optional. It simply skips to other keys.
Until now I didn't override the initialiser because we had all the supporting extensions for RealmOptional Realm Lists etc. But now I have to override the init(from decoder: Decoder) to decode it manually and the Realm model has more than 50 properties in it (You know what I mean).
If we override the initialiser I feel there is not point in using JSONDecoder because there is more manual work than using JSONDecoder.
required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(Int64.self, forKey: .id) ?? 0
name = try container.decodeIfPresent(String?.self, forKey: .name) ?? ""
numberOfStudents = try container.decodeIfPresent(RealmOptional<Int64>.self, forKey: .numberOfStudents) ?? RealmOptional<Int64>()
let classesArray = try container.decode([Class].self, forKey: .classes)
classes.append(objectsIn: classesArray)
}
So can someone suggest me the alternate solution to make the RealmOptional compatible with JSONDecoder so that we don't have to override the initialisers.
Here is something you can do to work around the problem. Create a new class which supports decoding and has RealmOptional as its property.
class OptionalInt64: Object, Decodable {
private var numeric = RealmOptional<Int64>()
required public convenience init(from decoder: Decoder) throws {
self.init()
let singleValueContainer = try decoder.singleValueContainer()
if singleValueContainer.decodeNil() == false {
let value = try singleValueContainer.decode(Int64.self)
numeric = RealmOptional(value)
}
}
var value: Int64? {
return numeric.value
}
var zeroOrValue: Int64 {
return numeric.value ?? 0
}
}
Then, instead of using RealmOptional in your school class, use this new OptionalInt64 class,
class School: Object, Codable {
#objc dynamic var id: Int64 = 0
#objc dynamic var name: String?
#objc dynamic var numberOfStudents: OptionalInt64?
var classes = List<Class>()
enum CodingKeys: String, CodingKey {
case id
case name
case numberOfStudents
case classes
}
}
Note that now instead of using RealmOptional, you are using RealmNumeric? which is of type Optional. Since, it is optional, automatic decoding uses decodeIfPresent method to decode the optional value. And if it is not present in json the value will simply become nil.
I have modified the solution of Sandeep to be more generic:
class RealmOptionalCodable<Value: Codable>: Object, Codable where Value: RealmSwift.RealmOptionalType {
private var numeric = RealmOptional<Value>()
var value: Value? {
get {
numeric.value
}
set {
numeric.value = newValue
}
}
required public convenience init(from decoder: Decoder) throws {
self.init()
let singleValueContainer = try decoder.singleValueContainer()
if singleValueContainer.decodeNil() == false {
let value = try singleValueContainer.decode(Value.self)
numeric = RealmOptional(value)
}
}
}
Using
#objc dynamic var numberOfStudents: RealmOptionalCodable<Int>?
Add #objcMembers above your Realm Model class.
Use variable as below
public dynamic var someValue = RealmOptional<Int>()
While assigning values to realm optional, you can use someValue.value = 10
By default someValue will be nil.
I found this solution and it works like a charm. I am using the updated code from srv7's comment.
Since last year, Realm added a new and more easier way for optionals, using #Persisted - docs
How to use it:
class Order: Object, Codable {
#Persisted(primaryKey: true) var productOrderId: Int?
#Persisted var name: String?
#Persisted var standardPrice: Double?
#Persisted var paid: Bool?
}