Serialization and generic

Hi,
I'm trying to create a generic container that I can back up to disk as needed and that I can restore later.
I first set up the following extensions

extension Data {
    public func deserialize<T:Codable>() -> T? {
        var lRet:T? = nil
        
        if let lValue = try? JSONDecoder().decode(T.self, from: self) {
            lRet = lValue
        } else if let lValue = try? JSONDecoder().decode([T].self, from: self) {
            lRet = lValue.first
        }
        
        return lRet
    }
}

extension Encodable {
    public func serialize() -> Data? {
        var lRet:Data? = nil

        if let lData = try? JSONEncoder().encode(self) {
            lRet = lData
        } else if let lData = try? JSONEncoder().encode([self]) {
            lRet = lData
        }
        
        return lRet
    }
}

I then created a class to hold different keys with different values

public class Store {
    private var dict:[String:Data] = [:]
    
    public init() {
    }
    
    public init(_ pData:Data) {
        self.load(pData)
    }
    
    public subscript<T>(_ pKey:String) -> T? where T:Codable {
        get {
            var lRet:T? = nil
            
            if let lData = dict[pKey], let lValue:T = lData.deserialize() {
                lRet = lValue
            }
            
            return lRet
        }
        set(pValue) {
            if let lValue = pValue?.serialize() {
                dict[pKey] = lValue
            } else {
                dict.removeValue(forKey: pKey)
            }
        }
    }
    
    public func data() -> Data {
        var lRet = Data()

        if let lData = dict.serialize() {
            lRet = lData
        }
        
        return lRet
    }
    
    public func load(_ pData:Data) {
        if let lDict:[String:Data] = pData.deserialize() {
            dict = lDict
        }
    }
}

And finally here is a piece of code to test

public class Test : Codable {
    public var int:Int
    public var string:String
    
    public init(int pInt:Int, string pString:String) {
        int = pInt
        string = pString
    }
}

let lStore = Store()

lStore["int"] = 100
lStore["string"] = "a string"
lStore["test"] = Test(int: 100, string: "just a test with class")

let lData = lStore.data()

let lNewStore = Store(lData)

let lInt:Int = lNewStore["int"] ?? 0
let lString:String = lNewStore["string"] ?? ""
let lTest:Test = lNewStore["test"] ?? Test(int: 0, string: "")

print("lInt = \(lInt), lString = \(lString), lTest=\(lTest)")

With basic types like Int or String it works fine
By cons when I try with a class I can manage to generate the corresponding Data and read it but it bug on the following lines

if let lData = dict[pKey], let lValue:T = lData.deserialize() {
    lRet = lValue
}

For lValue I have some thing like this:


And if I assign lValue to lRet, lRet is nil

What should I do for the class to be deserialized properly?

I'm using Xcode Version 10.2.1 (10E1001) with Swift 5.0.1

I deserialised your class Test successfully from the example. What was the problem?

Note that, the green line indicates the step you're currently on, it hasn't executed that line yet. That why lValue is a valid value (from your screenshot), and lRet is nil.

Meanwhile, I took the liberty of refactoring some of the sample code

extension Data {
    public func deserialize<T: Codable>() throws -> T {
        return try JSONDecoder().decode([T].self, from: self).first!
    }
}

extension Encodable {
    public func serialize() throws -> Data {
        // We could wait until JSON correctly encode Int/String at toplevel
        // (as per https://bugs.swift.org/browse/SR-7213),
        // might as well just do [T] at this point.
        return try JSONEncoder().encode([self])
    }
}

public class Store: Codable {
    private var dict: [String: Data] = [:]
    
    public subscript<T>(_ key: String) -> T? where T: Codable {
        get { return try? dict[key]?.deserialize() }
        set { dict[key] = try? newValue?.serialize() }
    }
}

let store = Store()

// ...

let data = try JSONEncoder().encode(store)
let newStore = try JSONDecoder().decode(Store.self, from: data)

I known about the green line
But when I go to the next step and assign lValue to lRet lRet is nil

I couldn't get nil at the end of the serialisation (not with Int, String, Test).

Is there any particular case that cause the problem?

Also, it'd be better to try to catch throwing exceptions, instead of silently nil-ing it.

I tried and if you try with your refactoring code you don't have the error It just happens with my code

With my code you can reproduce all the time the case
My code is not as compact as yours but does the same thing as yours
The only real difference is that I go through an intermediate variable (lValue in my code) to retrieve the result of the deserialization. When I assign this result to the return variable (lRet = lValue) I have no error, no exception but lRet does not get the value of lValue when it's a class
I have not seen any reason why lRet does not take the value of lValue and yet this is the case
Your more compact version works but it does not explain why lRet = lValue does not work

Among all the tests I did I also tried to catch exceptions and it did not change anything

Terms of Service

Privacy Policy

Cookie Policy