How to use List type with Codable? (RealmSwift)

血红的双手。 提交于 2019-12-18 04:16:45

问题


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,


回答1:


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)



回答2:


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




回答3:


There is only one problem with the accepted answer. Realm specifies that lists should be let(Constants) So to modify the solution to follow best practices you just have to make your list a let and then loop through appending the results to your array.

// Change this Line in [Your Code]
// to a let (Constant)

var tags = List<Tag>() to let tags = List<Tag>()

Then change

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

to

let tagsArray = try container.decode([Tag].self, forKey: .tags)   
tagsArray.forEach{ tags.append($0) }



回答4:


This is how you create your Realm / Swift 4 Codable Models.

For more Details with JSON Response, Model & URLSession: Read this article

import RealmSwift

class VendorsList : Object, Decodable {
    @objc dynamic var id : Int = 0
    @objc dynamic var name : String?
    @objc dynamic var logo : String?
    // Create your Realm List.
    var kitchensList = List<VendorKitchens>()

    override class func primaryKey() -> String? {
        return "id"
    }

    private enum CodingKeys: String, CodingKey {
        case id
        case name
        case logo
        // Set JSON Object Key
        case kitchensList = "kitchens"

    }

    public required convenience init(from decoder: Decoder) throws {
        self.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
        self.name = try container.decode(String.self, forKey: .name)
        self.logo = try container.decode(String.self, forKey: .logo)
        // Map your JSON Array response
        let kitchens = try container.decodeIfPresent([VendorKitchens].self, forKey: .kitchensList) ?? [VendorKitchens()]
        kitchensList.append(objectsIn: kitchens)

    }

}


class VendorKitchens : Object, Decodable {
    @objc dynamic var id : Int = 0
    @objc dynamic var name : String?

    override class func primaryKey() -> String? {
        return "id"
    }

    private enum CodingKeys: String, CodingKey {
        case id
        case name
    }
}


来源:https://stackoverflow.com/questions/45452833/how-to-use-list-type-with-codable-realmswift

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!