Codable variable in Codable Class - swift

Given this class:
class MyClass: Codable {
var variable : Codable? = nil
}
I get error:
Type 'MyClass' does not conform to protocol 'Decodable'
Type 'MyClass' does not conform to protocol 'Encodable'
How can I have a generic variable that conforms to Codable as a property in a Codable class?

May be like this if i got your question correctly
class MyClass<T: Codable>: Codable {
var variable : T? = nil
}

If you try to implement your decoder you will have something like:
class MyClass: Codable {
var variable: Codable?
enum MyClassKeys: String, CodingKey {
case variable = "variable"
}
required init(from decoder: Decoder) {
let container = try! decoder.container(keyedBy: MyClassKeys.self)
variable = try! container.decode(XXXX, forKey: .variable)
}
}
But instead of XXXX, you should put a class type (for example String.self). What can you put here? Its not possible for the compiler to guess what you want. Instead of Codable type maybe you can put a class which conform to Codable protocol. So you can have something like this:
class MyClass: Codable {
var variable: MyClass?
enum MyClassKeys: String, CodingKey {
case variable = "variable"
}
required init(from decoder: Decoder) {
let container = try! decoder.container(keyedBy: MyClassKeys.self)
variable = try! container.decode(MyClass.self, forKey: .variable)
}
}

You can't.
But as a workaround, you can create an enum conforming to Codable.
enum SubType: Codable {
case foo
case bar
}

Related

How to make `ObservableObject` with `#Published` properties `Codable`?

My codable observable Thing compiles:
class Thing: Codable, ObservableObject {
var feature: String
}
Wrapping feature in #Published though doesn’t:
class Thing: Codable, ObservableObject {
#Published var feature: String
}
🛑 Class 'Thing' has no initializers
🛑 Type 'Thing' does not conform to protocol 'Decodable'
🛑 Type 'Thing' does not conform to protocol 'Encodable'
Apparently Codable conformance can’t be synthesized anymore because #Published doesn’t know how to encode/decode its wrappedValue (because it doesn’t conform to Codable even if its wrapped value does)?
Okay, so I’ll let it know how to do it!
extension Published: Codable where Value: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue) // 🛑 'wrappedValue' is unavailable: #Published is only available on properties of classes
}
public init(from decoder: Decoder) throws {
var container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self) // 🛑 'wrappedValue' is unavailable: #Published is only available on properties of classes
}
}
So sorrow, much misery, many sadness! 😭
How can I easily add back Codable synthesis (or similar) without defining encode(to:) and init(from:)?
I don't know why you would want to do it, I think you should code the struct, not the published class. Something like this:
struct CodableStruct: Codable {
var feature1: String = ""
var feature2: Int = 0
}
class Thing: ObservableObject {
#Published var features: CodableStruct = .init()
}

Observable Object breaks Codable protocol compliance in observing struct despite conforming to Codable (SwiftUI)

I am confronted with a bit of a conundrum. I have an Observable Object MatrixStatus that is codable:
class MatrixStatus: ObservableObject, Codable{
#Published var recalcTriggerd = false
init(){}
//Codable protocol
enum CodingKeys: CodingKey{
case recalcTriggered
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(recalcTriggerd, forKey: .recalcTriggered)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
recalcTriggerd = try container.decode(Bool.self, forKey: .recalcTriggered)
}
}
It is set up as an environment object by a View of a document. The document observes it and remains codable. However, if I observe it in a different struct, MatrixData, that is a property of the document, the MatrixData struct stops conforming to Codable
struct MatrixData: Codable, Equatable{
#EnvironmentObject var calcStatus: MatrixStatus //triggers 'does not conform to codable'
...
Any suggestions how to solve, other than implementing the encode etc functions in MatrixData?
Thanks,
Lars

using DidSet or WillSet on decodable object

I currently have a Codable object which I am initializing using:
let decoder = JSONDecoder()
let objTest: TestOBJClass = try! decoder.decode(TestOBJClass.self, from: data)
TestOBJClass:
public class TestOBJClass: Codable {
var name: String
var lastname: String?
var age: Int? {
DidSet {
//Do Something
}
}
}
The object gets initialized with no issues. However, the DidSet function on the age variable is not getting called. Are we able to use these functions when working with Decodable or Encodable objects? Thank you for your help!
As #Larme pointed out in comments, property observers like didSet aren't called in initializers, including the one for Codable conformance. If you want that behavior, refactor the code from your didSet into a separate named function, and then at the end of your initializer call that function.
struct MyStruct: Codable
{
...
var isEnabled: Bool {
didSet { didSetIsEnabled() }
}
func didSetIsEnabled() {
// Whatever you used to do in `didSet`
}
init(from decoder: Decoder) throws
{
defer { didSetIsEnabled() }
...
}
}
Of course, that does mean you'll need to explicitly implement Codable conformance rather than relying on the compiler to synthesize it.

Explit conformance to Codable removes memberwise initializer generation on structs

Given:
struct Foo {
let bar: Bar
}
I get a convenience initializer to use:
let foo = Foo(bar: Bar())
But if Bar isn't itself Codable, or for some other reason I need to explicitly implement Codable on Foo then the convenience memberwise initializer is no longer present:
struct Foo: Codable {
init(from decoder: Decoder) throws {
}
func encode(to encoder: Encoder) throws {
}
let bar: Bar
}
and i get:
let foo = Foo(bar: Bar())
Incorrect argument label in call (have 'bar:', expected 'from:')
Is it possible to have the best of both worlds here?
You can implement the Codable conformance in an extension.
When adding any struct initializer in an extension, the memberwise initializer will not be removed.
struct MyStruct {
var name: String
}
extension MyStruct: Codable {} // preserves memberwise initializer
MyStruct(name: "Tim")

A codable structure contains a protocol property

I have a protocol which is inherited from codable
protocol OrderItem:Codable {
var amount:Int{get set}
var isPaid:Bool{get set}
}
And a struct conform this protocol
struct ProductItem:OrderItem {
var amount = 0
var isPaid = false
var price = 0.0
}
However when I put this structure into a codable structure, I got errors
struct Order:Codable {
var id:String
var sn:String = ""
var items:[OrderItem] = []
var createdAt:Int64 = 0
var updatedAt:Int64 = 0
}
The errors are
Type 'Order' does not conform to protocol 'Encodable'
Type 'Order' does not conform to protocol 'Decodable'
But if I change items:[OrderItem] to items:[ProductItem] , everything works!
How can I fix this problem?
You cannot do that because a protocol only states what you must do. So when you conform your protocol X to Codable, it only means that any type that conforms to X must also conform to Codable but it won't provide the required implementation. You probably got confused because Codable does not require you to implement anything when all your types already are Codable. If Codable asked you to, say, implement a function called myFunction, your OrderItem would then lack the implementation of that function and the compiler would make you add it.
Here is what you can do instead:
struct Order<T: OrderItem>: Codable {
var id:String
var sn:String = ""
var items: [T] = []
var createdAt:Int64 = 0
var updatedAt:Int64 = 0
}
You now say that items is a generic type that conforms to OrderItem.
It's worth mentioning that if you have a property of an array and the type is a protocol: let arrayProtocol: [MyProtocol] and the array contains multiple types that all conform to MyProtocol, you will have to implement your own init(from decoder: Decoder) throws to get the values and func encode(to encoder: Encoder) throws to encode them.
So for example:
protocol MyProtocol {}
struct FirstType: MyProtocol {}
struct SecondType: MyProtocol {}
struct CustomObject: Codable {
let arrayProtocol: [MyProtocol]
enum CodingKeys: String, CodingKey {
case firstTypeKey
case secondTypeKey
}
}
so our decode will look like this:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
// FirstType conforms to MyProtocol
let firstTypeArray = try values.decode([FirstType].self, forKey: .firstTypeKey)
// SecondType conforms to MyProtocol
let secondTypeArray = try values.decode([SecondType].self, forKey: .secondTypeKey)
// Our array is finally decoded
self.arrayProtocol: [MyProtocol] = firstTypeArray + secondTypeArray
}
and the same for encoded, we need to cast to the actual type before encoding:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let firstActualTypeArray = arrayProtocol.compactMap{$0 as? FirstType}
let secondActualTypeArray = arrayProtocol.compactMap{$0 as? SecondType}
try container.encode(firstActualTypeArray, forKey: .firstTypeKey)
try container.encode(secondActualTypeArray, forKey: .secondTypeKey)
}
Codable is a type alias for Encodable and Decodable.
Therefore if you're implementing it you need to implement the following two functions.
func encode(to: Encoder)
init(from: Decoder)