A.s.ALI
A.s.ALI

Reputation: 2082

How to implement Codable while using Realm

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

Upvotes: 14

Views: 8221

Answers (5)

Wo_0NDeR ᵀᴹ
Wo_0NDeR ᵀᴹ

Reputation: 623

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

Upvotes: 3

arturdev
arturdev

Reputation: 11039

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

Upvotes: 3

Oliver
Oliver

Reputation: 437

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 {

}

Upvotes: 5

Sushil
Sushil

Reputation: 147

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

Upvotes: 2

Andr&#233;
Andr&#233;

Reputation: 326

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)

Upvotes: 1

Related Questions