Using an Object with ream codable - ios

I have an application where I tried using UserDefaults to save a Codable model locally but the problem is that UserDefault does not store all the values of the Model and assigns nil to some of them and at times the proper value is assigned. So I decided to try an alternative which is realm and I am able to modify my model to work with realm but I have an issue not which is in the process of decoding an Object in an Object using realm. I was able to make it work when dealing with an Array object with List but non array Objects simply failed to map to JSON
below is a sample array that I am dealing with
{
"id": 732,
"name": "Vendor Name",
"logo": ".../thumb/missing.png",
"kitchens":
{
"id": 36,
"name": "Sandwiches"
}
}
model class
class VendorsList : Object, Decodable {
#objc dynamic var id : Int = 0
#objc dynamic var name : String?
#objc dynamic var logo : String?
// Create your Realm List.
var kitchensList = List<VendorKitchens>()
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case name
case logo
// Set JSON Object Key
case kitchensList = "kitchens"
}
public required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.logo = try container.decode(String.self, forKey: .logo)
// Map your JSON Array response
let kitchens = try container.decodeIfPresent([VendorKitchens].self, forKey: .kitchensList) ?? [VendorKitchens()]
kitchensList.append(objectsIn: kitchens)
}
}
class VendorKitchens : Object, Decodable {
#objc dynamic var id : Int = 0
#objc dynamic var name : String?
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case name
}
}
this returns an error
Failed to map data to JSON

Here, try this.
class VendorsList : Object, Decodable {
#objc dynamic var id : Int = 0
#objc dynamic var name : String?
#objc dynamic var logo : String?
#objc dynamic var kitchens: VendorKitchens? = nil
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case name
case logo
case kitchens = "kitchens"
}
public required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.logo = try container.decode(String.self, forKey: .logo)
kitchens = try container.decodeIfPresent(VendorKitchens.self, forKey: .kitchensList)
}
}
class VendorKitchens : Object, Decodable {
#objc dynamic var id : Int = 0
#objc dynamic var name : String?
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case name
}
}

Related

How do I properly use Codable, CoreData and NSSet relationships?

I'm following this tutorial to implement CoreData with Codable. Everything seems to be going great however I cannot figure out how to encode my list of photo objects. You can see my data structure in the image below and view my current code. When I try to decode the photos objects in the Pin class as below I get the error:
Referencing instance method 'encode(_:forKey:)' on 'Optional' requires that 'NSSet' conform to 'Encodable'
Photo+CoreDataClass.swift
import Foundation
import CoreData
#objc(Photo)
public class Photo: NSManagedObject, Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
do {
try container.encode(id, forKey: .id)
try container.encode(owner, forKey: .owner)
try container.encode(server, forKey: .server)
try container.encode(secret, forKey: .secret)
try container.encode(title, forKey: .title)
try container.encode(isPublic, forKey: .isPublic)
try container.encode(isFriend, forKey: .isFriend)
try container.encode(isFamily, forKey: .isFamily)
}
}
required convenience public init(from decoder: Decoder) throws {
guard let contextUserInfoKey = CodingUserInfoKey(rawValue: "context"),
let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Photo", in: managedObjectContext) else {
fatalError("Cannot decode Photo!")
}
self.init(entity: entity, insertInto: managedObjectContext)
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
id = try values.decode(Int64.self, forKey: .id)
owner = try values.decode(String?.self, forKey: .owner)
server = try values.decode(String?.self, forKey: .server)
secret = try values.decode(String?.self, forKey: .secret)
title = try values.decode(String?.self, forKey: .title)
isPublic = try values.decode(Int16.self, forKey: .isPublic)
isFriend = try values.decode(Int16.self, forKey: .isFriend)
isFamily = try values.decode(Int16.self, forKey: .isFamily)
} catch {
print(error)
}
}
enum CodingKeys: String, CodingKey {
case id = "id"
case owner = "owner"
case server = "server"
case secret = "secret"
case title = "title"
case isPublic = "ispublic"
case isFriend = "isfriend"
case isFamily = "isfamily"
}
}
Photo+CoreDataProperties.swift
import Foundation
import CoreData
extension Photo {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Photo> {
return NSFetchRequest<Photo>(entityName: "Photo")
}
#NSManaged public var id: Int64
#NSManaged public var owner: String?
#NSManaged public var secret: String?
#NSManaged public var server: String?
#NSManaged public var title: String?
#NSManaged public var isPublic: Int16
#NSManaged public var isFriend: Int16
#NSManaged public var isFamily: Int16
}
Pin+CoreDataClass.swift
import Foundation
import CoreData
#objc(Pin)
public class Pin: NSManagedObject, Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)
try container.encode(photos, forKey: .photos)
}
required convenience public init(from decoder: Decoder) throws {
guard let contextUserInfoKey = CodingUserInfoKey(rawValue: "context"),
let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Pin", in: managedObjectContext) else {
fatalError("Could not decode Pin!")
}
self.init(entity: entity, insertInto: managedObjectContext)
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
latitude = try values.decode(Double.self, forKey: .latitude)
longitude = try values.decode(Double.self, forKey: .longitude)
photos = NSSet(array: try values.decode([Photo].self, forKey: .photos))
} catch {
print(error)
}
}
enum CodingKeys: String, CodingKey {
case latitude = "latitude"
case longitude = "longitude"
case photos = "photos"
}
}
Pin+CoreDataProperties.swift
import Foundation
import CoreData
extension Pin {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Pin> {
return NSFetchRequest<Pin>(entityName: "Pin")
}
#NSManaged public var latitude: Double
#NSManaged public var longitude: Double
#NSManaged public var photos: NSSet?
}
// MARK: Generated accessors for photos
extension Pin {
#objc(addPhotosObject:)
#NSManaged public func addToPhotos(_ value: Photo)
#objc(removePhotosObject:)
#NSManaged public func removeFromPhotos(_ value: Photo)
#objc(addPhotos:)
#NSManaged public func addToPhotos(_ values: NSSet)
#objc(removePhotos:)
#NSManaged public func removeFromPhotos(_ values: NSSet)
}
Declare photos as a swift native type
#NSManaged var photos: Set<Photo>
In decoder
photos = try values.decode(Set<Photo>.self, forKey: .photos)

Is it possible to have an array of structs which conform to the same protocol also support Codable?

I have setup the following protocol, and have 2 structs which then conform to this protocol:
protocol ExampleProtocol: Decodable {
var name: String { get set }
var length: Int { get set }
}
struct ExampleModel1: ExampleProtocol {
var name: String
var length: Int
var otherData: Array<String>
}
struct ExampleModel2: ExampleProtocol {
var name: String
var length: Int
var dateString: String
}
I want to deserialise some JSON data I receive from the server, and I know it will be returning a mix of both ExampleModel1 and ExampleModel2 in an array:
struct ExampleNetworkResponse: Decodable {
var someString: String
var modelArray: Array<ExampleProtocol>
}
Is there anyway to use the Codable approach and support both models easily? Or will I need to manually deserialise the data for each model?
EDIT 1:
Conforming to Decodable on the structs, still gives the same results:
struct ExampleModel1: ExampleProtocol, Decodable {
enum CodingKeys: String, CodingKey {
case name, length, otherData
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.length = try container.decode(Int.self, forKey: .length)
self.otherData = try container.decode(Array<String>.self, forKey: .otherData)
}
var name: String
var length: Int
var otherData: Array<String>
}
struct ExampleModel2: ExampleProtocol, Decodable {
enum CodingKeys: String, CodingKey {
case name, length, dateString
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.length = try container.decode(Int.self, forKey: .length)
self.dateString = try container.decode(String.self, forKey: .dateString)
}
var name: String
var length: Int
var dateString: String
}
struct ExampleNetworkResponse: Decodable {
var someString: String
var modelArray: Array<ExampleProtocol>
}
If you have a limited amount of ExampleProtocols and you need to have a different type of ExampleProtocols in the same array, then you can create a holder for ExampleProtocol and use it for decoding/encoding.
ExampleHolder could hold all possible Decodable ExampleProtocol types in one array. So decoder init don't need to have so many if-else scopes and easier to add more in the future.
Would recommend keeping ExampleHolder as a private struct. So it's not possible to access it outside of file or maybe even not outside of ExampleNetworkResponse.
enum ExampleNetworkResponseError: Error {
case unsupportedExampleModelOnDecoding
}
private struct ExampleHolder: Decodable {
let exampleModel: ExampleProtocol
private let possibleModelTypes: [ExampleProtocol.Type] = [
ExampleModel1.self,
ExampleModel2.self
]
init(from decoder: Decoder) throws {
for type in possibleModelTypes {
if let model = try? type.init(from: decoder) {
exampleModel = model
return
}
}
throw ExampleNetworkResponseError.unsupportedExampleModelOnDecoding
}
}
struct ExampleNetworkResponse: Decodable {
var someString: String
var modelArray: Array<ExampleProtocol>
enum CodingKeys: String, CodingKey {
case someString, modelArray
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
someString = try container.decode(String.self, forKey: .someString)
let exampleHolderArray = try container.decode([ExampleHolder].self, forKey: .modelArray)
modelArray = exampleHolderArray.map({ $0.exampleModel })
}
}
–––––––––––––––––––––––––––––––––
If in one response can have only one type of ExampleProtocol in the array then:
struct ExampleNetworkResponse2<ModelArrayElement: ExampleProtocol>: Decodable {
var someString: String
var modelArray: Array<ModelArrayElement>
}
usage:
let decoder = JSONDecoder()
let response = try decoder.decode(
ExampleNetworkResponse2<ExampleModel1>.self,
from: dataToDecode
)

Issue with Model class of Realm

I was going through some model classes of a project(Realm is being used). This is one class…
#objcMembers class CommA: Object {
dynamic var id = 0
dynamic var recipientId = "0"
dynamic var name: String?
dynamic var picture: String?
dynamic var unreadMessageCount = 0
dynamic var lastMessage: MyMessage?
override class func primaryKey() -> String? {
return "id"
}
}
This pretty much seems easy. A class with variables and a primary key defined..
But there is another class which looks like so…
#objcMembers class CommB: Object, Codable {
dynamic var id = "0"
dynamic var name: String?
dynamic var picture: String?
dynamic var status: String?
dynamic var lastSeen: String?
dynamic var unreadMessageCount = 0
dynamic var lastMessage: MyMessage?
enum CodingKeys: String, CodingKey {
case id = "UsrID"
case name = "UserName"
case picture = "UsrPicture"
case status = "ChatStatus"
}
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, picture: String, status: String) {
self.init()
self.id = id
self.name = name
self.picture = picture
self.status = status
}
convenience required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let name = try container.decode(String.self, forKey: .name)
let picture = try container.decode(String.self, forKey: .picture)
//let status = try container.decode(String.self, forKey: .status)
self.init(id: id, name: name, picture: picture, status: "status")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(picture, forKey: .picture)
try container.encode(status, forKey: .status)
}
override class func primaryKey() -> String? {
return "id"
}
}
What I don’t understand here is why the enum, required init, convenience required init etc. is used…?
In fact, some of the initialisers are redundant. Your code can be shortened to
#objcMembers class CommB: Object, Codable {
dynamic var id = "0"
dynamic var name: String?
dynamic var picture: String?
dynamic var status: String?
dynamic var lastSeen: String?
dynamic var unreadMessageCount = 0
dynamic var lastMessage: MyMessage?
enum CodingKeys: String, CodingKey {
case id = "UsrID"
case name = "UserName"
case picture = "UsrPicture"
case status = "ChatStatus"
}
convenience init(id: String, name: String, picture: String, status: String) {
self.init()
self.id = id
self.name = name
self.picture = picture
self.status = status
}
convenience required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let name = try container.decode(String.self, forKey: .name)
let picture = try container.decode(String.self, forKey: .picture)
//let status = try container.decode(String.self, forKey: .status)
self.init(id: id, name: name, picture: picture, status: "status")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(picture, forKey: .picture)
try container.encode(status, forKey: .status)
}
override class func primaryKey() -> String? {
return "id"
}
}
I have removed
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)
}
Everything else is quite important.
CommB is not only a realm object, but also Codable. And the author of it wanted to customise the de/encoding behaviour so that the de/encoder only de/encodes id, name, picture and status. To do this, a CodingKey enum needs to be created, storing the coding keys. Also, convenience required init(from decoder: Decoder) and func encode(to encoder: Encoder) needs to be implemented.
The convenience init(id: String, name: String, picture: String, status: String) initialiser is there because the init(from decoder: Decoder) delegates to it.
To learn more about how Codable works, visit here.

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
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 {
}
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.
An example model with Codable implementation
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
}
}
}
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: "user#domain.com", 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)
}
}
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)

How to store a dictionary [String:[Object]]?

So I have a class called Restaurant and a [String:[Restaurant]] dictionary. How should I properly store this dictionary so that data inside it wouldn't be lost after user relaunches the app?
class Restaurant {
var name: String?
init?(name: String) {
guard !name.isEmpty else {
return nil
}
self.name = name
}
}
var restaurants = [String: [Restaurant]]()
override func viewDidLoad() {
super.viewDidLoad()
// ...
restaurants["Pizza"] = ...
}
Try using realm.io
If you want to conform both decodable and the realm object to create the object in disk you could do it as follow
import RealmSwift
class Restaurant: Object, Decodable {
#objc dynamic var name: String?
private enum CodingKeys: String, CodingKey { case name}
convenience init( name: String?){
self.init()
self.name = name
}
convenience required init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
func saveToDisk(){
let realm = try! Realm()
try! realm.write {
realm.add(restaurant)
}
}
You can adapt it to save an array of Restaurant Object
You can use other different methods as:
CoreData
Using a file
UserDefaults(too slow and not for large amount of data)

Resources