1

I have different types of objects that conform to the same protocol. Currently, I found the way to encode these objects to the array, but not the way to decode them back

There is my Playground

import UIKit
import SceneKit

extension SCNVector3 {
    func toArr() -> [Float] {
        return [self.x, self.y, self.z]
    }
}

extension Array where Element == Float {
    func toSCNVector3() -> SCNVector3? {
        guard self.count < 3 else { return nil }
        return SCNVector3Make(self[0], self[1], self[2])
    }
}

protocol Parsable : Codable {
    var innerObj: InnerObject? { get set }
    var type: Types { get set }
}

class InnerObject: Codable {
    var vector: SCNVector3? = SCNVector3.init(3.1, 4.1, 5.1)
    
    enum CodingKeys: CodingKey {
        case vector
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(vector?.toArr(), forKey: .vector)
    }
    
    init() { }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        vector = try container.decode([Float].self, forKey: .vector).toSCNVector3()
    }
}

enum Types: String, Codable {
    case first, second
}

protocol FirstImpl: Parsable {
    var name: String? { get set }
}

protocol SecondImpl: Parsable {
    var name: String? { get set }
}

struct AnyEncodable: Encodable {
    let encodeFunction: (Encoder) throws -> Void
    
    init(_ encodable: Encodable) {
        encodeFunction = encodable.encode(to:)
    }
    
    func encode(to encoder: Encoder) throws {
        try encodeFunction(encoder)
    }
}

struct ParcerableDecodable: Decodable {
    init(from decoder: Decoder) throws {
        var container: UnkeyedDecodingContainer = try decoder.unkeyedContainer()
        var obj: Parsable?
        
        while !container.isAtEnd {
          obj = try container.decode(FirstImpl.self)
        }
    }
}

class One: FirstImpl {
    var type: Types = .first
    
    var name: String? = "first"
    var innerObj: InnerObject? = InnerObject()
    
    enum CodingKeys: CodingKey {
        case name, innerObj, type
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(type, forKey: .type)
        try container.encode(name, forKey: .name)
        try container.encode(innerObj, forKey: .innerObj)
    }
    
    init() { }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        name = try container.decode(String.self, forKey: .name)
        innerObj = try container.decode(InnerObject.self, forKey: .innerObj)
        type = try container.decode(Types.self, forKey: .type)
    }
}

class Second: SecondImpl {
    var type: Types = .second
    var innerObj: InnerObject? = InnerObject()
    var name: String? = "second"
}

do {
    let first: Codable = One()
    let second: Codable = Second()
    
    
    let arr = [first, second]
    let data: Data = try JSONEncoder().encode(arr.map(AnyEncodable.init))
    let stringFist = String(data: data, encoding: .utf8)!
    print(stringFist)

    let decoder = JSONDecoder()
    let arrayBack: [ParcerableDecodable] = try decoder.decode([ParcerableDecodable].self, from: data)
}

The problem is that these objects conform to the same protocol and there is no way to use a protocol while decoding

Are there any ideas?

Sirop4ik
  • 4,543
  • 2
  • 54
  • 121
  • 2
    You should have a property of type `[Parcerable]` in the `ParcerableDecodable` wrapper, for you to put the decoded `Parcerable`s in. Also, decode the concrete types `One` and `Two`, not the protocols. Use `try?` so that you can check if the decoding failed or not. If it failed, try a different concrete type. – Sweeper Dec 23 '21 at 16:22
  • ParceableDecodable could decode ´type’ and then return One or Second according to it. – Ptit Xav Dec 23 '21 at 19:05
  • You could also encode the class name for type, and when decoding parseableDecodable use it with NSClassFromString – Ptit Xav Dec 23 '21 at 19:11
  • @Sweeper thanks:) Found the solution and posted the answer:) – Sirop4ik Dec 24 '21 at 14:18
  • @Sweeper do you have an idea about this question - https://stackoverflow.com/q/70564894/5709159 – Sirop4ik Jan 03 '22 at 11:11

1 Answers1

0

Eventually (thanks to @Sweeper) the final solution looks like this

import Cocoa
import SceneKit

extension SCNVector3 {
    func toArr() -> [Float] {
        return [Float(self.x), Float(self.y), Float(self.z)]
    }
}

extension Array where Element == Float {
    func toSCNVector3() -> SCNVector3? {
        guard self.count <= 3 else { return nil }
        return SCNVector3Make(CGFloat(self[0]), CGFloat(self[1]), CGFloat(self[2]))
    }
}

protocol Parsable : Codable {
    var innerObj: InnerObject? { get set }
    var type: Types { get set }
}

protocol FirstImpl: Parsable {
    var name: String? { get set }
}

protocol SecondImpl: Parsable {
    var name: String? { get set }
}


enum Types: String, Codable {
    case first, second
}

class InnerObject: Codable {
    var vector: SCNVector3? = SCNVector3.init(3.1, 4.1, 5.1)
    
    enum CodingKeys: CodingKey {
        case vector
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(vector?.toArr(), forKey: .vector)
    }
    
    init() { }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        vector = try container.decode([Float].self, forKey: .vector).toSCNVector3()
    }
}

class ViewController: NSViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("")
        // Do any additional setup after loading the view.
        foo()
    }
    
    override var representedObject: Any? {
        didSet {
            // Update the view, if already loaded.
        }
    }
    
    struct AnyEncodable: Encodable {
        let encodeFunction: (Encoder) throws -> Void
        
        init(_ encodable: Encodable) {
            encodeFunction = encodable.encode(to:)
        }
        
        func encode(to encoder: Encoder) throws {
            try encodeFunction(encoder)
        }
    }

    struct ParcerableDecodable: Decodable {
        private(set) var parerableArr = [Parsable]()
        
        init(from decoder: Decoder) throws {
            var container: UnkeyedDecodingContainer = try decoder.unkeyedContainer()

            while !container.isAtEnd {
                if let obj = try? container.decode(One.self) {
                    parerableArr.append(obj)
                }
                else if let obj = try? container.decode(Second.self) {
                    parerableArr.append(obj)
                }
                else {
                    //MARK: no match
                }
            }
        }
    }
    
    class One: FirstImpl {
        var type: Types = .first
        
        var name: String? = "first"
        var innerObj: InnerObject? = InnerObject()
        
        enum CodingKeys: CodingKey {
            case name, innerObj, type
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(type, forKey: .type)
            try container.encode(name, forKey: .name)
            try container.encode(innerObj, forKey: .innerObj)
        }
        
        init() { }
        
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            
            guard let type = try? container.decode(Types.self, forKey: .type) else {
                let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "ERROR: no type field in the given object", underlyingError: nil)
                throw DecodingError.typeMismatch(One.self, context)
            }
            
            if type != self.type {
                let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Type \(type), doesn't match to needed one \(type), so skip:)", underlyingError: nil)
                throw DecodingError.typeMismatch(One.self, context)
            }
            
            name = try container.decode(String.self, forKey: .name)
            innerObj = try container.decode(InnerObject.self, forKey: .innerObj)
        }
    }
    
    class Second: SecondImpl {
        var type: Types = .second
        var innerObj: InnerObject? = InnerObject()
        var name: String? = "second"
    }
    
    private func foo() {
        let first: Codable = One()
        let second: Codable = Second()
        
        
        let arr = [first, second]
        let data: Data? = try? JSONEncoder().encode(arr.map(AnyEncodable.init))
        let stringFist = String(data: data!, encoding: .utf8)!
        print(stringFist)
        
        let decoder = JSONDecoder()
        let arrayBack: ParcerableDecodable? = try? decoder.decode(ParcerableDecodable.self, from: data!)
        print(arrayBack?.parerableArr.count)
    }
}
Sirop4ik
  • 4,543
  • 2
  • 54
  • 121