I'm trying to save some data from textFields in an iOS app in swift with codable, but when I define the function for archiving xcode tells me "Generic parameter 'Value' could not be inferred".
I'm learning about archiving, if someone can help me I'll be very greatful.
This is the code that I'm using
class ProfileViewController: UITableViewController {
var currentProfile: Profile?
#IBOutlet weak var name: UITextField!
#IBOutlet weak var surname: UITextField!
#IBAction func saveProfileButton(_ sender: Any) {
addCurrentProfile()
saveProfile()
}
func addCurrentProfile(){
currentProfile?.name = name.text ?? ""
currentProfile?.surname = surname.text ?? ""
}
struct Profile {
var name: String
var surname: String
enum CodingKeys: String, CodingKey {
case name
case surname
}
init(name:String, surname:String) {
self.name = name
self.surname = surname
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(surname, forKey: .surname)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
surname = try container.decode(String.self, forKey: .surname)
}
}
func saveProfile() {
do {
let data = try PropertyListEncoder().encode(currentProfile)
let success = NSKeyedArchiver.archiveRootObject(data, toFile: profile_data.path)
print(success ? "Successful save" : "Save Failed")
} catch {
print("Save Failed")
}
}
}
First thing is to make your struct confirm to Codable protocol like:
struct Profile:Codable {
var name: String
var surname: String
enum CodingKeys: String, CodingKey {
case name
case surname
}
init(name:String, surname:String) {
self.name = name
self.surname = surname
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(surname, forKey: .surname)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
surname = try container.decode(String.self, forKey: .surname)
}
}
then, you can achieve the object using PropertyListEncoder as below:
func saveProfile() {
do {
let data = try PropertyListEncoder().encode(currentProfile)
let success = NSKeyedArchiver.archiveRootObject(data, toFile: profile_data.path)
print(success ? "Successful save" : "Save Failed")
} catch {
print("Save Failed")
}
}
It seems there are several questions here.
The direct answer to your question :
"Generic parameter 'Value' could not be inferred".
is: you forget to add the protocol:
struct Profile: Codable {
...}
Related
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)
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
)
I have created a model class from [https://jsonmaster.github.io/#][1] this link in codable, I am getting this kind of error
Cannot convert value of type '[[BookingMasterModel]?]' to expected argument type '[BookingMasterModel].Type'
Actually I am very new to it please share if there is the solution for it
//MyOrdersModel
import Foundation
struct MyOrdersModel: Codable {
let BookingMasterModel: [BookingMasterModel]?
let RESPONSE: String?
let RESID: String?
private enum CodingKeys: String, CodingKey {
case BookingMasterModel = "BookingMasterModel"
case RESPONSE = "RESPONSE"
case RESID = "RES_ID"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
BookingMasterModel = try values.decodeIfPresent([BookingMasterModel].self, forKey: .BookingMasterModel)
RESPONSE = try values.decodeIfPresent(String.self, forKey: .RESPONSE)
RESID = try values.decodeIfPresent(String.self, forKey: .RESID)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(BookingMasterModel, forKey: .BookingMasterModel)
try container.encodeIfPresent(RESPONSE, forKey: .RESPONSE)
try container.encodeIfPresent(RESID, forKey: .RESID)
}
}
this Booking Master model
import Foundation
struct BookingMasterModel: Codable {
let AMOUNT: String?
let APPTDATE: String?
let CANCELLEDON: String?
let COUNT: String?
let DATE: String?
let MOBILE: String?
let NAMES: String?
let ORDERBY: String?
let ORDERMODE: String?
let ORDERNO: String?
let REMINDER: String?
let SERVICEDON: String?
let STATUS: String?
private enum CodingKeys: String, CodingKey {
case AMOUNT = "AMOUNT"
case APPTDATE = "APPT_DATE"
case CANCELLEDON = "CANCELLED_ON"
case COUNT = "COUNT"
case DATE = "DATE"
case MOBILE = "MOBILE"
case NAMES = "NAMES"
case ORDERBY = "ORDER_BY"
case ORDERMODE = "ORDER_MODE"
case ORDERNO = "ORDER_NO"
case REMINDER = "REMINDER"
case SERVICEDON = "SERVICED_ON"
case STATUS = "STATUS"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
AMOUNT = try values.decodeIfPresent(String.self, forKey: .AMOUNT)
APPTDATE = try values.decodeIfPresent(String.self, forKey: .APPTDATE)
CANCELLEDON = try values.decodeIfPresent(String.self, forKey: .CANCELLEDON)
COUNT = try values.decodeIfPresent(String.self, forKey: .COUNT)
DATE = try values.decodeIfPresent(String.self, forKey: .DATE)
MOBILE = try values.decodeIfPresent(String.self, forKey: .MOBILE)
NAMES = try values.decodeIfPresent(String.self, forKey: .NAMES)
ORDERBY = try values.decodeIfPresent(String.self, forKey: .ORDERBY)
ORDERMODE = try values.decodeIfPresent(String.self, forKey: .ORDERMODE)
ORDERNO = try values.decodeIfPresent(String.self, forKey: .ORDERNO)
REMINDER = try values.decodeIfPresent(String.self, forKey: .REMINDER)
SERVICEDON = try values.decodeIfPresent(String.self, forKey: .SERVICEDON)
STATUS = try values.decodeIfPresent(String.self, forKey: .STATUS)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(AMOUNT, forKey: .AMOUNT)
try container.encodeIfPresent(APPTDATE, forKey: .APPTDATE)
try container.encodeIfPresent(CANCELLEDON, forKey: .CANCELLEDON)
try container.encodeIfPresent(COUNT, forKey: .COUNT)
try container.encodeIfPresent(DATE, forKey: .DATE)
try container.encodeIfPresent(MOBILE, forKey: .MOBILE)
try container.encodeIfPresent(NAMES, forKey: .NAMES)
try container.encodeIfPresent(ORDERBY, forKey: .ORDERBY)
try container.encodeIfPresent(ORDERMODE, forKey: .ORDERMODE)
try container.encodeIfPresent(ORDERNO, forKey: .ORDERNO)
try container.encodeIfPresent(REMINDER, forKey: .REMINDER)
try container.encodeIfPresent(SERVICEDON, forKey: .SERVICEDON)
try container.encodeIfPresent(STATUS, forKey: .STATUS)
}
}
I would recommend you to use your struct without coding keys because Codable protocol can do this for you.
But your error comes from wrong naming of struct properties. You named your MyOrdersModel property BookingMasterModel same as BookingMasterModel. Replace this variable name with small capital letter.
let bookingMasterModel: [BookingMasterModel]?
also replace let keyword of all properties to var
var bookingMasterModel: [BookingMasterModel]?
var RESPONSE: String?
var RESID: String?
do the same in BookingMasterModel struct.
Now, finally delete all coding keys and inits to make it easier.
struct MyOrdersModel: Codable {
var bookingMasterModel: [BookingMasterModel]?
var RESPONSE: String?
var RESID: String?
}
struct BookingMasterModel: Codable {
var AMOUNT: String?
var APPTDATE: String?
var CANCELLEDON: String?
var COUNT: String?
var DATE: String?
var MOBILE: String?
var NAMES: String?
var ORDERBY: String?
var ORDERMODE: String?
var ORDERNO: String?
var REMINDER: String?
var SERVICEDON: String?
var STATUS: String?
}
Note: you can read more about Swift naming convention here
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.
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)