Search code examples
swiftrealm-mobile-platformrealm-list

How to model a Swift dictionary property on a Realm object?


How should I model a dictionary property on a Realm object so when encoded to JSON I can get this:

{
    "firstName": "John",
    "lastName": "Doe",
    "favoriteThings": {
        "car": "Audi R8",
        "fruit": "strawberries",
        "tree": "Oak"
    }
}

I tried creating a new Object FavoriteThings with 'key' and 'value' properties as I've seen elsewhere...

public class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    var favoriteThings = List<FavoriteThings>()
}

But List gives me an array, naturally, when I encode it to JSON. I don't want an array. I'm using Swift Codable.

{
    "firstName": "John",
    "lastName": "Doe",
    "favoriteThings": [
    {
      "key": "fruit",
      "value": "strawberries"
    },
    {
      "key": "tree",
      "value": "Oak"
    }
    ],
}

Any pointers appreciated!

Gonzalo


Solution

  • As you know, Lists are encoded into json arrays by default. So, to encode a List into a Dictionary you'll have to implement a custom encode method to do exactly that.

    public class FavoriteThings: Object {
        @objc dynamic var key = ""
        @objc dynamic var value = ""
    
        convenience init(key: String, value: String) {
            self.init()
            self.key = key
            self.value = value
        }
    }
    
    public class Person: Object, Encodable {
    
        enum CodingKeys: String, CodingKey {
            case firstName
            case lastName
            case favoriteThings
        }
    
        @objc dynamic var firstName = ""
        @objc dynamic var lastName = ""
        let favoriteThings = List<FavoriteThings>()
    
        convenience init(firstName: String, lastName: String, favoriteThings: [FavoriteThings]) {
            self.init()
            self.firstName = firstName
            self.lastName = lastName
            self.favoriteThings.append(objectsIn: favoriteThings)
        }
    
        public func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(firstName, forKey: .firstName)
            try container.encode(lastName, forKey: .lastName)
    
            var favThings: [String: String] = [:]
            for thing in favoriteThings {
                favThings[thing.key] = thing.value
            }
    
            try container.encode(favThings, forKey: .favoriteThings)
        }
    }
    

    And the usage would be like this:

    func testEncode() {
        let john = Person(
            firstName: "John",
            lastName: "Doe",
            favoriteThings: [
                .init(key: "car", value: "Audi R8"),
                .init(key: "fruit", value: "strawberries"),
                .init(key: "tree", value: "Oak"),
        ])
    
        let encoder = JSONEncoder()
        let data = try! encoder.encode(john)
        if let string = String(data: data, encoding: .utf8) {
            print(string)
        }
    }
    

    Which prints:

    {"firstName":"John","favoriteThings":{"car":"Audi R8","fruit":"strawberries","tree":"Oak"},"lastName":"Doe"}