Search code examples
jsonswiftswiftuirealm

JSON and Realm — do I have to maintain separate structs / classes?


When I receive JSON from an API call, when decoding it I have structs like this:

import Foundation

struct JSON: Codable {
    
    var alpha: Alpha
    var beta: Beta
    var gamma: [Gamma]?
    
}

I want to save the JSON in my Realm database, to later use and traverse like JSON. It's my understanding that I can't just use the existing structs I have written, instead I have to rewrite a second (similar) class like this to use with Realm:

import Foundation
import RealmSwift

class RealmJSON: Object, Identifiable {
    
    @Persisted (primaryKey: true) var id: ObjectId
    
    @Persisted var alpha: RealmAlpha
    @Persisted var beta: RealmBeta
    @Persisted var gamma: RealmSwift.List<RealmGamma>?
    
    override class func primaryKey() -> String? {
        "id"
    }
    
    convenience init(id: ObjectId, alpha: RealmAlpha, beta: RealmBeta, gamma: RealmSwift.List<RealmGamma>?) {
        
        self.init()
        
        self.id = id
        self.alpha = alpha
        self.beta = beta
        self.gamma = gamma
    }
    
}

Obviously, this is inconvenient especially when dealing with large amounts of JSON. Moreover I want to use Swagger codegen to write the client code for me, but it kind of defeats the purpose if I then have to manually add the Realm classes manually.

Is this the only way for dealing with JSON and a Realm database, or am I missing something here?

EDIT: I realise a simple way is to store most of the JSON as a raw JSON string with properties to identify schema type / version. Then I can just fetch the correct schema I require and parse the rawJSON string with the existing JSON structs...


Solution

  • You can pass the json data directly to your objects. I can think of two ways.

    The first way, conform to Codable.

    class Dog: Object, Codable {
        @Persisted var name: String
    }
    
    class Cat: Object, Codable {
        @Persisted var name: String
    }
    
    class Kid: Object, Codable {
        @Persisted var name: String
    }
    
    class Owner: Object, Codable {
        @Persisted var name: String
        @Persisted var dog: Dog?
        @Persisted var cat: Cat?
        @Persisted var kids: List<Kid>
    }
    

    Let's use the following method to make json data:

    func makeData() -> Data {
        let string = """
            {
                "name": "Tom",
                "kids": [{"name": "Penelope"}, {"name": "Rob"}],
                "cat": {"name": "Lilly"},
                "dog": {"name": "Lucy"}
            }
        """
        return string.data(using: .utf8)!
    }
    

    Now we can create our objects:

    func decodeOwner() {
        let decoder = JSONDecoder()
        let owner = try! decoder.decode(Owner.self, from: makeData())
        print("Decoded:", owner)
    }
    

    Another way is to use JSONSerialization and use the result to pass to the value constructor:

    extension Object {
        convenience init(json: Data) throws {
            let data = try JSONSerialization.jsonObject(with: json)
            self.init(value: data)
        }
    }
    
    func serializeOwner() {
        let owner = try! Owner(json: makeData())
        print("Serialization:", owner)
    }