How to implement Codable while using Realm
Asked Answered
S

5

14

Hy I am working on app that uses Realm and Alamofire. I am really happy in using these library in my iOS project.

But then I have to post a List of models that contains multiple lists of models. So that is too much deep thing I mean List inside List that contains models and those model contains list of several model

For demonstration lets just take an example of my models

@objcMembers public class MyModel : Object{
    dynamic var Id: String = ""
    dynamic var Name: String = ""
    dynamic var Favorites: List<String>? = nil 
    dynamic var Subjects: List<UserSubject>? = nil 
}


@objcMembers public class UserSubject: Object{
    dynamic var Id: String = ""
    dynamic var Name: String = ""
    dynamic var Teachers: List<Publications>? = nil 
}


@objcMembers public class Publications: Object{
    dynamic var Id: String = ""
    dynamic var Name: String = ""
    dynamic var Editors: List<Editors>? = nil 
}

So you can see these are models inside list that contains another list of model.

Due to Realm I am using List for list for creating the RelationShip.

Problem: but Now when I tries to implement Codable on Models/Struct It really unable to recognize List property.

I really do not know how to solve this problem? Do anyone have any Idea how to do it ?

UPDATE: I am using Swift 4.0 and base sdk is 11.2

Shortchange answered 16/11, 2018 at 6:44 Comment(0)
N
5

Had the same problem in a project and wrote these extensions:

import Foundation
import RealmSwift

extension RealmSwift.List: Decodable where Element: Decodable {
    public convenience init(from decoder: Decoder) throws {
        self.init()
        let container = try decoder.singleValueContainer()
        let decodedElements = try container.decode([Element].self)
        self.append(objectsIn: decodedElements)
    }
}

extension RealmSwift.List: Encodable where Element: Encodable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self.map { $0 })
    }
}

With these extension you easily can make the Realm Object Codable. Like this

@objcMembers public class MyModel: Object, Codable {
    dynamic var id: String = ""
    dynamic var name: String = ""
    var favorites = List<String>()
    var subjects = List<UserSubject>()
}

@objcMembers public class UserSubject: Object, Codable {
    dynamic var id: String = ""
    dynamic var name: String = ""
    var teachers = List<Publications>()
}


@objcMembers public class Publications: Object, Codable {
    dynamic var id: String = ""
    dynamic var name: String = ""
    var editors = List<Editor>()
}

@objcMembers public class Editor: Object, Codable {

}
Nobelium answered 23/11, 2018 at 14:8 Comment(4)
it gives me error "Extension of type 'List' with constraints cannot have an inheritance clause"Shortchange
Properties which are a List<T> do not have to be dynamic. Did you make all elements implement the Codable protocol?Nobelium
I do not know how to implement it that is main problemShortchange
I updated my answer which is now containing your classes with the Codable protocol. Actually i's very easy: When you make a class Codable all it members have to be Codable as well.Nobelium
H
3

I can suggest you use Unrealm.
It's a powerful library which enables you to save Swift native types (structs, enums, Arrays, Dictionaries) into Realm database. So you don't have to worry about Lists and Codable compatibility anymore.

enter image description here

An example model with Codable implementation

Halfpint answered 25/9, 2019 at 8:26 Comment(4)
thanks man for replying. I have solved it somehow. but can you take a look at here #58074910Shortchange
If you use Unrealm you will lost all the realm realtime notifications, or live object's !Rafe
@Wo_0NDeR That's true.. but it's a matter of taste! Ones decide to encapsulate the local persistency under lower layers and do not expose it's internal logic to the upper layers (View, ViewModel, etc..), others decide to do the opposite :)Halfpint
don't use this library, it's not being maintained, last issue was closed more than a year agoPostcard
I
3

For these using Swift 5.x and XCode 13.x.x (i have 13.3.1) and RealmSwift (10.25.1):

Realm with Codable (Encode/Decode) (2 class for example)

import Foundation
import RealmSwift

/*
 * Hold info about user profile
 */
class Profile: Object, Codable {
    @Persisted(primaryKey: true) var _id: String
    @Persisted var firstName: String
    @Persisted var lastName: String
    @Persisted var email: String
    @Persisted var role: String
    
    // Relations
    @Persisted var session: Session?
    @Persisted var companies: List<Company>
    
    // MARK: Codable support
    enum CodingKeys: String, CodingKey {
        case email, companies
        case id = "_id"
        case firstName, lastName, role
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(_id, forKey: .id)
        try container.encode(firstName, forKey: .firstName)
        try container.encode(lastName, forKey: .lastName)
        try container.encode(email, forKey: .email)
        try container.encode(role, forKey: .role)
        try container.encode(companies, forKey: .companies)
    }
    
    required init(from decoder: Decoder) throws {
        super.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        _id = try container.decode(String.self, forKey: .id)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)
        email = try container.decode(String.self, forKey: .email)
        role = try container.decode(String.self, forKey: .role)
        let companiesList = try container.decode([Company].self, forKey: .companies)
        companies.append(objectsIn: companiesList)
    }
}

Other example:

import Foundation
import RealmSwift

/*
 * Hold info about user session
 */
class Session: Object, Codable {
    @Persisted(primaryKey: true) var _id: String
    @Persisted(indexed: true) var accessToken: String
    @Persisted var refreshToken: String
    @Persisted var tokenType: String
    @Persisted var expiresIn: Double
    
    // Relations
    @Persisted var profile: Profile?
    
    // MARK: Codable support
    enum CodingKeys: String, CodingKey {
        case accessToken = "access_token"
        case tokenType = "token_type"
        case expiresIn = "expires_in"
        case refreshToken = "refresh_token"
        case id = "_id"
        case v = "__v"
        case profile
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(_id, forKey: .id)
        try container.encode(accessToken, forKey: .accessToken)
        try container.encode(refreshToken, forKey: .refreshToken)
        try container.encode(tokenType, forKey: .tokenType)
        try container.encode(expiresIn, forKey: .expiresIn)
        try container.encode(profile, forKey: .profile)
    }
    
    required init(from decoder: Decoder) throws {
        super.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        _id = try container.decode(String.self, forKey: .id)
        accessToken = try container.decode(String.self, forKey: .accessToken)
        refreshToken = try container.decode(String.self, forKey: .refreshToken)
        tokenType = try container.decode(String.self, forKey: .tokenType)
        expiresIn = try container.decode(Double.self, forKey: .expiresIn)
        profile = try container.decode(Profile.self, forKey: .profile)
    }
}

You can encode List from realm with this code, for example:

try container.encode(companies, forKey: .companies)

and to decode:

let companiesList = try container.decode([Company].self, forKey: .companies)
companies.append(objectsIn: companiesList)

This is only and example, you can adapt to your need's.

And finally for example when you get data from network (i'm using Moya):

extension Session {
    init(data: Data) throws {
        self = try JSONDecoder().decode(Session.self, from: data)
    }
}

self.xxApi.request(.login(username: "[email protected]", password: "HiTh3r3.2022")) { result in
            
            switch result {
            case let .success(response):
                guard let session = try? Session(data: response.data) else {
                    print("Can't parse session data: \(JSON(response.data))")
                    return
                }
                
                // Request parsed so work with data here
                print(session)
            case let .failure(error):
                print(error)
            }
        }
Ideatum answered 6/5, 2022 at 20:27 Comment(0)
S
2

You can use extensions for List

Swift 4.1

extension List: Decodable  where Element: Decodable {
    public convenience init(from decoder: Decoder) throws {
    // Initialize self here so we can get type(of: self).
    self.init()
    assertTypeIsDecodable(Element.self, in: type(of: self))
    let metaType = (Element.self as Decodable.Type) // swiftlint:disable:this force_cast

    var container = try decoder.unkeyedContainer()
    while !container.isAtEnd {
        let element = try metaType.init(__from: &container)
        self.append(element as! Element) // swiftlint:disable:this force_cast
    }
  }
}

extension List: Encodable where Element: Decodable {
    public func encode(to encoder: Encoder) throws {
       assertTypeIsEncodable(Element.self, in: type(of: self))
       var container = encoder.unkeyedContainer()
       for element in self {
           // superEncoder appends an empty element and wraps an Encoder around it.
           // This is normally appropriate for encoding super, but this is really what we want to do.
           let subencoder = container.superEncoder()
           try (element as! Encodable).encode(to: subencoder) // swiftlint:disable:this force_cast
       }
   }
}
Spoony answered 16/11, 2018 at 23:47 Comment(7)
it is error prone. first error it is giving on extension line is "Extension of type List with constraints can not have an inheritence clause"Shortchange
Above Code Snippet is for Swift 4+Spoony
This uses conditional conformances which were added in Swift 4.1Brewhouse
Yes, Thank you SvenSpoony
it gives me following errors 1> Extention of type 'List' with constraints cannot have an inheritance clause 2> Used of unresolved identifier 'assertTypeIsDecodable' 3> Incorrect argument label in call (have '__form; expected 'from:')Shortchange
Please confirm that you are using swift 4.1Spoony
I am using 4.0...... though I have done a work around. But this problem must be addressShortchange
S
1

Try this:

extension List: Decodable where Element: Decodable {
    public convenience init(from decoder: Decoder) throws {
        self.init()
        var container = try decoder.unkeyedContainer()
        while !container.isAtEnd {
            let element = try container.decode(Element.self)
            self.append(element)
        }
    }
}

extension List: Encodable where Element: Encodable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        for element in self {
            try element.encode(to: container.superEncoder())
        }
    }
}

Found it here: How to use List type with Codable? (RealmSwift)

Sandra answered 22/11, 2018 at 18:46 Comment(5)
it gives me error "Extension of type 'List' with constraints cannot have an inheritance clause"Shortchange
Are you importing "Realm" or "RealmSwift"?Famulus
I am importing RealmSwiftShortchange
Do you have any other List extensions? Is it in a separated file with RealmSwift imported? Did you try cleaning the project before building? Sorry to ask, but I remember seeing this error when I tried to implement this extension and it was solved under these conditions!Famulus
No I have not a single extension related to RealmSwiftShortchange

© 2022 - 2025 — McMap. All rights reserved.