如何使用RealmSwift存储字典?

26

考虑以下模型:

class Person: Object {
    dynamic var name = ""
    let hobbies = Dictionary<String, String>()
}

我正在尝试将一个类型为[String:String]的对象从Alamofire请求存储到Realm中,但是无法实现,因为根据RealmSwift文档,由于它是List<T>/Dictionary<T,U>类型,hobbies必须通过let进行定义。

let hobbiesToStore: [String:String]
// populate hobbiestoStore
let person = Person()
person.hobbies = hobbiesToStore

我也尝试重新定义init(),但最终总是出现致命错误或其他问题。

在RealSwift中如何简单地复制或初始化字典?我是否遗漏了一些微不足道的东西?

5个回答

37

Dictionary 在 Realm 中不支持作为属性类型。 你需要引入一个新的类,该类对象描述每个键值对和与之相关的一对多关系,如下所示:

class Person: Object {
    dynamic var name = ""
    let hobbies = List<Hobby>()
}

class Hobby: Object {
    dynamic var name = ""
    dynamic var descriptionText = ""
}

对于反序列化,您需要将JSON中的字典结构映射到Hobby对象,并将键和值分配给适当的属性。


谢谢!我也考虑过这个解决方案(因为它是最干净的),但是不能在RealmSwift中使用任何Swift结构真的很令人沮丧...(甚至没有元组:()。由于我的数据非常静态和简单,我最终将两个字符串与分隔符合并,并创建了一个单独的List<String> - gabuchan
有一些限制阻止我们支持任何通用的Swift结构,特别是元组。其中包括我们必须能够在运行时确定类型,并能够通过动态访问器返回值。这对于元组来说是不起作用的。 - marius
更新2021:现在支持Map类型。请参见下面的我的答案 - Morpheus

30

我目前通过在我的模型上公开一个被忽略的字典属性来进行模拟,该属性由一个私有的、持久化的NSData支持,该NSData封装了字典的JSON表示形式:

class Model: Object {
    private dynamic var dictionaryData: NSData?
    var dictionary: [String: String] {
        get {
            guard let dictionaryData = dictionaryData else {
                return [String: String]()
            }
            do {
                let dict = try NSJSONSerialization.JSONObjectWithData(dictionaryData, options: []) as? [String: String]
                return dict!
            } catch {
                return [String: String]()
            }
        }

        set {
            do {
                let data = try NSJSONSerialization.dataWithJSONObject(newValue, options: [])
                dictionaryData = data
            } catch {
                dictionaryData = nil
            }
        }
    }

    override static func ignoredProperties() -> [String] {
        return ["dictionary"]
    }
}

这可能不是最有效的方式,但它允许我继续使用Unbox,将传入的JSON数据快速、轻松地映射到我的本地Realm模型。


1
请注意,额外的JSON(反)序列化会对性能产生影响,并且您将失去以这种方式查询字典的能力。 - marius
嗨@marius,当然可以。这是一种解决方法,正如我所说的,并不是最有效的方法,但它适用于我需要在我的Realm上拥有字典引用的情况(我实际上并不需要查询)。希望我们能够在某个时候看到对字典的本地支持,这样就不再需要这个了。 - boliva

6

我建议将字典保存为JSON字符串存储在Realm中。然后检索JSON并转换为字典。使用以下扩展。

extension String{
func dictionaryValue() -> [String: AnyObject]
{
    if let data = self.data(using: String.Encoding.utf8) {
        do {
            let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject]
            return json!

        } catch {
            print("Error converting to JSON")
        }
    }
    return NSDictionary() as! [String : AnyObject]
} }

并且

extension NSDictionary{
    func JsonString() -> String
    {
        do{
        let jsonData: Data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
        return String.init(data: jsonData, encoding: .utf8)!
        }
        catch
        {
            return "error converting"
        }
    }
}

很棒的解决方案! - jcpennypincher
1
这是最简单的解决方案,使得在 Realm 中存储字典变得容易。 - jcpennypincher
2
太慢了,建议使用KeyArchiver。 - Peter Lapisu

5

更新2021

自从Realm 10.8.0版本开始,使用Map类型可以在Realm对象中存储字典。

以下是来自官方文档的示例:

class Dog: Object {
    @objc dynamic var name = ""
    @objc dynamic var currentCity = ""
    // Map of city name -> favorite park in that city
    let favoriteParksByCity = Map<String, String>()
}

如何对Map进行解码/编码? 当我写Map<String,String>时,出现了错误: 类型“QuestionList”不符合协议“Decodable”|| 类型“QuestionList”不符合协议“Encodable” - Ufuk Köşker

1
也许有点低效,但对我来说有效(例如从Int->String的字典,类似于您的示例):
class DictObj: Object {
   var dict : [Int:String] {
      get {
         if _keys.isEmpty {return [:]} // Empty dict = default; change to other if desired
         else {
            var ret : [Int:String] = [:];
            Array(0..<(_keys.count)).map{ ret[_keys[$0].val] = _values[$0].val };
            return ret;
         }
      }
      set {
         _keys.removeAll()
         _values.removeAll()
         _keys.appendContentsOf(newValue.keys.map({ IntObj(value: [$0]) }))
         _values.appendContentsOf(newValue.values.map({ StringObj(value: [$0]) }))
      }
   }
   var _keys = List<IntObj>();
   var _values = List<StringObj>();

   override static func ignoredProperties() -> [String] {
      return ["dict"];
   }
}

Realm无法存储字符串/整数列表,因为它们不是对象,所以可以创建“虚假对象”:

class IntObj: Object {
   dynamic var val : Int = 0;
}

class StringObj: Object {
   dynamic var val : String = "";
}

受到Stack Overflow上另一篇关于类似存储数组的答案的启发(目前无法找到该帖子)...

如果您有一个带有时间Int或Double等值,该怎么办?最好的解决方案是使用Data对象和JSONSerialization。 - Andrew Kochulab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接