Serializing JSON into Realm database with LinkingObjects

Patrick Edge
3 min readApr 7, 2019

--

Imagine we're building a investing app. We have our portoflio and available products to invest in. Every PortfolioItem is associated with one Product, and at some point in time we might want to refer to PortfolioItem from Product object via inverse relationship. To make this even more challenging we're receiving JSON data and storing them in a Realm database. So how to build our model classes in this case?

I'll show you how I built my classes first and then give a little bit of an explanation.

import Foundation
import Realm
import RealmSwift
class PortfolioItem: Object, Decodable {

@objc dynamic var id: String = ""
@objc dynamic var productId: String = ""
@objc dynamic var balance: Double = 0.0

@objc dynamic public var product: Product?

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

private enum CodingKeys: String, CodingKey {
case id, productId, balance
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

id = try container.decode(String.self, forKey: .id)
productId = try container.decode(String.self, forKey: .productId)
balance = try container.decode(Double.self, forKey: .balance)
super.init()
}

required init() {
super.init()
}

required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}

required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}

convenience init(id: String, productId: String, balance: Double) {
self.init()
self.id = id
self.productId = productId
self.balance = balance
}

}
final class Product: Object, Decodable {

// basic product info
@objc dynamic var id: String = ""
@objc dynamic var name: String = ""

private let portfolioItems = LinkingObjects(fromType: PortfolioItem.self, property: "product")

public var portfolioItem: PortfolioItem? {
return portfolioItems.first
}

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

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

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
super.init()
}

required init() {
super.init()
}

required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}

required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}

convenience init(id: String, name: String) {
self.init()
self.id = id
self.name = name
}

}

We're using Realm so we need to declare our properties with @Objc and set their initial values. We don't explicitly need convenience init, I have it just for testing. What we do need is these 3 initializer methods (don't forget to import Realm)

required init() {
super.init()
}

required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}

required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}

Also set your primary key (it has to have unique value):

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

CodingKeys are the key! If we need and inverse relationship we need to setup our CodingKeys correctly. It's actually pretty simple. In the CodingKeys enum incude just thoses value you want to parse from the JSON (notice how I don't include portfolioItem and portfolioItems). So this is how we implement it for Product:

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

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
super.init()
}

Also note that LinkingObjects are an array so I declared them as private and set up a computed property that returns firstObject of that array (because the relationship is 1:1 it will have only one object anyway).

private let portfolioItems = LinkingObjects(fromType: PortfolioItem.self, property: "product")

public var portfolioItem: PortfolioItem? {
return portfolioItems.first
}

And we're done here. Happy coding!

--

--