How to Use List Type with Codable? (Realmswift)

How to use List type with Codable? (RealmSwift)

You are almost there. Inside the initializer, you can initialize the list using the decoded array. Basically, change

tags = try container.decode(List<Tag>.self, forKey: .tags)   // this is problem.

to

let tagsArray = try container.decode([Tag].self, forKey: .tags)   
tags = List(tagsArray) // Now you are good

As pointed out in the comments the List constructor no longer works like this

You now want:

tags.append(objectsIn: tagsArray)

How to encode Realm's List type

To make a Realm object model class with a property of type List conform to Encodable, you can simply convert the List to an Array in the encode(to:) method, which can be encoded automatically.

extension User: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.username, forKey: .username)
let dogsArray = Array(self.dogs)
try container.encode(dogsArray, forKey: .dogs)
}
}

Test classes I used (slightly different from the ones in your question, but I already had these on hand and the methods in question will be almost identical regardless of the variable names):

class Dog: Object,Codable {
@objc dynamic var id:Int = 0
@objc dynamic var name:String = ""
}

class User: Object, Decodable {
@objc dynamic var id:Int = 0
@objc dynamic var username:String = ""
@objc dynamic var email:String = ""
let dogs = List<Dog>()

private enum CodingKeys: String, CodingKey {
case id, username, email, dogs
}

required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
username = try container.decode(String.self, forKey: .username)
email = try container.decode(String.self, forKey: .email)
let dogsArray = try container.decode([Dog].self, forKey: .dogs)
dogs.append(objectsIn: dogsArray)
}
}

Test the encoding/decoding:

let userJSON = """
{
"id":1,
"username":"John",
"email":"example@ex.com",
"dogs":[
{"id":2,"name":"King"},
{"id":3,"name":"Kong"}
]
}
"""

do {
let decodedUser = try JSONDecoder().decode(User.self, from: userJSON.data(using: .utf8)!)
let encodedUser = try JSONEncoder().encode(decodedUser)
print(String(data: encodedUser, encoding: .utf8)!)
} catch {
print(error)
}

Output:

{"username":"John","dogs":[{"id":2,"name":"King"},{"id":3,"name":"Kong"}]}

RealmSwift and Codable when using optional types

Vin Gazoli gave me the missing key.

First, RealmOptional needs to be declared as a let, so in the init you need to set the value by using myObject.myVariable.value = newValue. Then, everywhere you use it you must use it as obj.variable.value as well. RealmOptional does not conform to codable though, so you must write an extension. You can find it below as well as a link to where I received it.

ex of object:

class Attendee: Object,Codable {

@objc dynamic var id = 0
@objc dynamic var attendeeId = 0
@objc dynamic var lanId = ""
@objc dynamic var firstName = ""
@objc dynamic var lastName = ""
@objc dynamic var email = ""
@objc dynamic var employeeId = ""
@objc dynamic var badgeId = ""
@objc dynamic var department = ""
@objc dynamic var picture:String? = nil
let scanIn = RealmOptional<Double>()
let scanOut = RealmOptional<Double>()

override static func primaryKey () -> String? {
return "id"
}

private enum CodingKeys: String, CodingKey {
case id, attendeeId, lanId, firstName, lastName, email, employeeId, badgeId, department, picture, scanIn, scanOut
}

required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey:.id)
attendeeId = try container.decodeIfPresent(Int.self, forKey:.attendeeId) ?? 0
lanId = try container.decode(String.self, forKey:.lanId)
firstName = try container.decode(String.self, forKey:.firstName)
lastName = try container.decode(String.self, forKey:.lastName)
email = try container.decode(String.self, forKey:.email)
employeeId = try container.decode(String.self, forKey:.employeeId)
badgeId = try container.decode(String.self, forKey:.badgeId)
department = try container.decode(String.self, forKey:.department)
picture = try container.decodeIfPresent(String.self, forKey:.picture)
self.scanIn.value = try container.decodeIfPresent(Double.self, forKey:.scanIn) ?? 0
self.scanOut.value = try container.decodeIfPresent(Double.self, forKey:.scanOut) ?? 0
}

The below code is REQUIRED to make the above object function. Retrieved from this link in conjunction with h1m5's fix in the comments of that page. The below is for Double. The link has other primitives.

func assertTypeIsEncodable<T>(_ type: T.Type, in wrappingType: Any.Type) {
guard T.self is Encodable.Type else {
if T.self == Encodable.self || T.self == Codable.self {
preconditionFailure("\(wrappingType) does not conform to Encodable because Encodable does not conform to itself. You must use a concrete type to encode or decode.")
} else {
preconditionFailure("\(wrappingType) does not conform to Encodable because \(T.self) does not conform to Encodable.")
}
}
}

func assertTypeIsDecodable<T>(_ type: T.Type, in wrappingType: Any.Type) {
guard T.self is Decodable.Type else {
if T.self == Decodable.self || T.self == Codable.self {
preconditionFailure("\(wrappingType) does not conform to Decodable because Decodable does not conform to itself. You must use a concrete type to encode or decode.")
} else {
preconditionFailure("\(wrappingType) does not conform to Decodable because \(T.self) does not conform to Decodable.")
}
}
}

extension RealmOptional : Encodable where Value : Encodable {
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Value.self, in: type(of: self))

var container = encoder.singleValueContainer()
if let v = self.value {
try (v as Encodable).encode(to: encoder) // swiftlint:disable:this force_cast
} else {
try container.encodeNil()
}
}
}

extension RealmOptional : Decodable where Value : Decodable {
public convenience init(from decoder: Decoder) throws {
// Initialize self here so we can get type(of: self).
self.init()
assertTypeIsDecodable(Value.self, in: type(of: self))

let container = try decoder.singleValueContainer()
if !container.decodeNil() {
let metaType = (Value.self as Decodable.Type) // swiftlint:disable:this force_cast
let element = try metaType.init(from: decoder)
self.value = (element as! Value) // swiftlint:disable:this force_cast
}
}
}

How to make the RealmSwift RealmOptional compatible with Swift Codable?

Here is something you can do to work around the problem. Create a new class which supports decoding and has RealmOptional as its property.

class OptionalInt64: Object, Decodable {
private var numeric = RealmOptional<Int64>()

required public convenience init(from decoder: Decoder) throws {
self.init()

let singleValueContainer = try decoder.singleValueContainer()
if singleValueContainer.decodeNil() == false {
let value = try singleValueContainer.decode(Int64.self)
numeric = RealmOptional(value)
}
}

var value: Int64? {
return numeric.value
}

var zeroOrValue: Int64 {
return numeric.value ?? 0
}
}

Then, instead of using RealmOptional in your school class, use this new OptionalInt64 class,

class School: Object, Codable {

@objc dynamic var id: Int64 = 0

@objc dynamic var name: String?
@objc dynamic var numberOfStudents: OptionalInt64?
var classes = List<Class>()

enum CodingKeys: String, CodingKey {
case id
case name
case numberOfStudents
case classes
}
}

Note that now instead of using RealmOptional, you are using RealmNumeric? which is of type Optional. Since, it is optional, automatic decoding uses decodeIfPresent method to decode the optional value. And if it is not present in json the value will simply become nil.



Related Topics



Leave a reply



Submit