Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use List type with Codable? (RealmSwift)

Problem is List type does not conform to Codable, the below class cannot be insert to Realm.

for example,

class Book: Codable {
    var name: String = ""
    var author: String = ""
    var tags = [String]()
}

Consider the above class conforms to Codable, if store this class to Realm, it needs to use List<Object> type instead of [String]

class Book: Object, Codable {
    @objc dynamic var name: String = ""
    @objc dynamic var author: String = ""
    var tags = List<Tag>()

    required convenience init(from decoder: Decoder) throws {
        self.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        author = try container.decode(String.self, forKey: .author)
        tags = try container.decode(List<Tag>.self, forKey: .tags)   // this is problem.
    }
}

class Tag: Object, Codable {
    @objc dynamic var string: String = ""

    required convenience init(from decoder: Decoder) throws {
        self.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        string = try container.decode(String.self, forKey: .string)
    }
}

To conform to Codable, it should be implement Decodable protocol. (required convenience init(from decoder: Decoder) throws)

But, List type does not conform to Codable(Decodable), it is impossible to use Codable if the class has List type.

How to resolve this issue?

Thanks,

like image 237
Yosuke Imairi Avatar asked Aug 02 '17 06:08

Yosuke Imairi


2 Answers

We can use an extension to make List conform to Codable:

extension List : Decodable where Element : Decodable {
    public convenience init(from decoder: Decoder) throws {
        self.init()
        var container = try decoder.unkeyedContainer()
        while !container.isAtEnd {
            let element = try container.decode(Element.self)
            self.append(element)
        }
    } }

extension List : Encodable where Element : Encodable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        for element in self {
            try element.encode(to: container.superEncoder())
        }
    } }

I also got an extension for RealmOptional, if others need.

https://gist.github.com/ansonyao/41137bb3cbbca8ef31a13b6bc96ee422

like image 105
Anson Yao Avatar answered Oct 23 '22 06:10

Anson Yao


You are almost there. Inside the initializer, you can initialize the list using the decoded array. Basically, change

tags = try container.decode(List<Tag>.self, forKey: .tags)   // this is problem.

to

let tagsArray = try container.decode([Tag].self, forKey: .tags)   
tags = List(tagsArray) // Now you are good

As pointed out in the comments the List constructor no longer works like this

You now want:

tags.append(objectsIn: tagsArray)
like image 38
Guillermo Alvarez Avatar answered Oct 23 '22 06:10

Guillermo Alvarez