Realm结合HandyJSON或Codable的坑

1,818 阅读1分钟

一、问题

  1. 对于使用HandyJsonCodable来解析的数据转成Realm实例时失败!!!
  2. 转成Realm实例时HandyJson自定义映射无效!!!

二、解决方案

1. HandyJson

问题1:
let JsonString = """
{"genres":[{"id":28,"name":"Action"},{"id":12,"name":"Adventure"},{"id":16,"name":"Animation"},{"id":35,"name":"Comedy"},{"id":80,"name":"Crime"},{"id":99,"name":"Documentary"}]}
"""

class Genres: Object, HandyJSON {
    @objc dynamic var id: String = ""
    @objc dynamic var name: String?

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

    override required init() {
        super.init()
    }
}

let list: [Genres] = ([Genres].deserialize(from: JsonString, designatedPath: "genres") ?? []).compactMap { $0 }
list.forEach { print($0.id, $0.name) }

这里Realm对象的属性不可使用@Persisted标注,应该使用@objc dynamic标注为OC的动态类型。使用HandyJson有个便捷之处是解析时可以直接将idInt转成String

问题2:

这里有两种解决思路,直接上代码:

let JsonString = """
{"genres":[{"id":28,"name":"Action"},{"id":12,"name":"Adventure"},{"id":16,"name":"Animation"},{"id":35,"title":"Comedy"},{"id":80,"title":"Crime"},{"id":99,"title":"Documentary"}]}
"""

class Genres: Object, HandyJSON {
    @objc dynamic var id: String = ""
    @objc dynamic var name: String?
    private var title: String?

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

    override required init() {
        super.init()
    }

    /// 转换完成后赋值
    func didFinishMapping() {
        name = [name, title].compactMap { $0 }.first
    }
}

let list: [Genres] = ([Genres].deserialize(from: JsonString, designatedPath: "genres") ?? []).compactMap { $0 }
list.forEach { print($0.id, $0.name) }

这种方式是在HandyJsondidFinishMapping中直接给属性赋值。再看另一种方式:

let JsonString = """
{"genres":[{"id":28,"name":"Action"},{"id":12,"name":"Adventure"},{"id":16,"name":"Animation"},{"id":35,"title":"Comedy"},{"id":80,"title":"Crime"},{"id":99,"title":"Documentary"}]}
"""

class Genres: Object, HandyJSON {
    @objc dynamic var id: String = ""
    @objc dynamic var name: String?
    @objc private var title: String? {
        willSet {
            name = newValue
        }
    }

    override class func ignoredProperties() -> [String] {
        ["title"]
    }

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

    override required init() {
        super.init()
    }
}

let list: [Genres] = ([Genres].deserialize(from: JsonString, designatedPath: "genres") ?? []).compactMap { $0 }
list.forEach { print($0.id, $0.name) }

这里需要注意title属性前面加了@objc修饰表示这是OC属性,这样HandyJson在对title赋值时willSet将被调起,我们可以在willSet中对name进行赋值。如果不希望title属性也存储到Realm中,可以在RealmignoredProperties方法中标注。

执行代码后可以验证结果:

image-20221028132243665

Realm的表结构可以看出,只有idnametitle并不存在,其次name的值也是完整的,这也符合我们的预期。

2. Codable

let JsonString = """
{"genres":[{"id":28,"name":"Action"},{"id":12,"name":"Adventure"},{"id":16,"name":"Animation"},{"id":35,"name":"Comedy"},{"id":80,"name":"Crime"},{"id":99,"name":"Documentary"}]}
"""

class GenresModel: Object, Codable {
    @Persisted(primaryKey: true) var id: Int
    @Persisted var name: String?

    override required init() {
        super.init()
    }
}

let list = ([GenresModel].decodeJSON(from: JsonString, designatedPath: "genres") ?? []).compactMap { $0 }
list.forEach { print($0.id, $0.name) }

这里Realm对象的属性可以使用@Persisted标注。使用Codable解析时需要注意匹配数据类型,不可以直接将idInt转成String,当然你也可以写映射方法来实现。