How can I store a Dictionary with RealmSwift?

19,325

Solution 1

Dictionary is not supported as property type in Realm. You'd need to introduce a new class, whose objects describe each a key-value-pair and to-many relationship to that as seen below:

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

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

For deserialization, you'd need to map your dictionary structure in your JSON to Hobby objects and assign the key and value to the appropriate property.

Solution 2

I am currently emulating this by exposing an ignored Dictionary property on my model, backed by a private, persisted NSData which encapsulates a JSON representation of the dictionary:

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"]
    }
}

It might not be the most efficient way but it allows me to keep using Unbox to quickly and easily map the incoming JSON data to my local Realm model.

Solution 3

I would save the dictionary as JSON string in Realm. Then retrive the JSON and convert to dictionary. Use below extensions.

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]
} }

and

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"
        }
    }
}

Solution 4

UPDATE 2021

Since Realm 10.8.0, it is possible to store a dictionary in a Realm object using the Map type.

Example from the official documentation:

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>()
}

Solution 5

Perhaps a little inefficient, but works for me (example dictionary from Int->String, analogous for your example):

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 can't store a List of Strings/Ints because these aren't objects, so make "fake objects":

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

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

Inspired by another answer here on stack overflow for storing arrays similarly (post is eluding me currently)...

Share:
19,325

Related videos on Youtube

gabuchan
Author by

gabuchan

This is not an iOS/Front-end developer.

Updated on September 16, 2022

Comments

  • gabuchan
    gabuchan over 1 year

    Considering the following model:

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

    I'm trying to stock in Realm an object of type [String:String] that I got from an Alamofire request but can't since hobbies has to to be defined through let according to RealmSwift Documentation since it is a List<T>/Dictionary<T,U> kind of type.

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

    I also tried to redefine init() but always ended up with a fatal error or else.

    How can I simply copy or initialize a Dictionary in RealSwift? Am I missing something trivial here?

  • gabuchan
    gabuchan over 8 years
    Thanks! I've thought of this solution as well (since it's the cleanest one) but it's just really frustrating not to be able to use any Swift structures in RealmSwift... (not even tuples :( ). As my data is really static and simple, I ended up merging the two strings together with a delimiter and created a single List<String>.
  • marius
    marius over 8 years
    There are limitations which prevent us from being able to support any generic Swift structures especially tuples. Among them are that we must be able to figure out the type at runtime and be able to return the value by a dynamic accessor. That doesn't work with tuples.
  • marius
    marius almost 8 years
    Please be aware of the performance impact by the additional JSON (de-)serialization and that you loose the capability to query on the dictionary in that way.
  • boliva
    boliva almost 8 years
    hi @marius, of course. This is a workaround and, as I said, not the most efficient way to do it, but it works for the cases where I need to have a dictionary reference on my Realm (which I don't really need to query). Hopefully we'll get to see native support for dictionaries at some point, in which case this won't be needed anymore.
  • jcpennypincher
    jcpennypincher almost 6 years
    Great solution.!
  • jcpennypincher
    jcpennypincher over 5 years
    This is the simplest solution and makes storing a dictionary in Realm easy.
  • Peter Lapisu
    Peter Lapisu almost 5 years
    is slow as hell too, would use keyarchiver
  • Andrew Kochulab
    Andrew Kochulab almost 4 years
    What you do if you have a value with time Int or Double, etc? The best solution will be to use the Data object and JSONSerialization.
  • Coconuts
    Coconuts almost 3 years
    UPDATE 2021: the Map type is now supported. Please see my answer below.
  • Ufuk Köşker
    Ufuk Köşker over 2 years
    How Can I decode / encode Map ? I got error when I write Map<String , String>: Type 'QuestionList' does not conform to protocol 'Decodable' || Type 'QuestionList' does not conform to protocol 'Encodable'