Im facing an issue where I can't make the RealmOptional compatible with swift new Codable feature with json decoder.
Cosider the following Realm object.
class School: Object, Codable {
@objc dynamic var id: Int64 = 0
@objc dynamic var name: String?
var numberOfStudents = RealmOptional<Int64>()
var classes = List<Class>()
enum CodingKeys: String, CodingKey {
case id
case name
case numberOfStudents
case classes
}
}
class Class: Object, Codable {
var name: String?
var numberOfStudents = RealmOptional<Int64>()
}
Here we can declare the class as Codable because I wrote an extension for RealmOptinal with the help of this gist. But the problem is when the decoder decodes the json.
Consider this json
let jsonData = """
[
"id": 1234,
"name": "Shreesha",
"numberOfStudents": nil,
"classes": {
"name": "Class V",
"numberOfStudents": 12
}
]
""".data(using: .utf8)!
In this json all the data are passed and this decodes perfectly with the code.
let decoder = JSONDecoder()
let decoded = try! decoder.decode(School.self, from: jsonData)
But if I remove the numberOfStudents
key from the json data which supposed to be a RealmOptional object it will throw an error and it will not decode because RealmOptional is not a swift optional so the decoder thinks that there should be a key in the json data. In JSONDecoder
it doesn't try to decode if the key is not there in the json and the property is declared as optional. It simply skips to other keys.
Until now I didn't override the initialiser because we had all the supporting extensions for RealmOptional
Realm Lists
etc. But now I have to override the init(from decoder: Decoder)
to decode it manually and the Realm model has more than 50 properties in it (You know what I mean).
If we override the initialiser I feel there is not point in using JSONDecoder
because there is more manual work than using JSONDecoder.
required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(Int64.self, forKey: .id) ?? 0
name = try container.decodeIfPresent(String?.self, forKey: .name) ?? ""
numberOfStudents = try container.decodeIfPresent(RealmOptional<Int64>.self, forKey: .numberOfStudents) ?? RealmOptional<Int64>()
let classesArray = try container.decode([Class].self, forKey: .classes)
classes.append(objectsIn: classesArray)
}
So can someone suggest me the alternate solution to make the RealmOptional compatible with JSONDecoder
so that we don't have to override the initialisers.
Many applications use Realm Swift – from chart-topping apps in the App Store to the world’s smallest cloud-hosted, SwiftUI chat app. Learn how Realm helps teams build better apps, faster. “ Realm makes the data persistence layer so easy on iOS. We have a big advantage in terms of developer speed and general feature development.
Here are the advantages of Codable protocol: 1) Type safety. You don’t need typecasting or parsing the strings read from the file. Swift does for you all the low-level reading and parsing only returning you a ready to use object of a concrete type. 2) The Simplicity of usage.
It’s hard to imagine modern Swift iOS application that doesn’t work with multiple data sources like servers, local cache DB, etc, or doesn’t parse/convert data between different formats.
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.
I have modified the solution of Sandeep to be more generic:
class RealmOptionalCodable<Value: Codable>: Object, Codable where Value: RealmSwift.RealmOptionalType {
private var numeric = RealmOptional<Value>()
var value: Value? {
get {
numeric.value
}
set {
numeric.value = newValue
}
}
required public convenience init(from decoder: Decoder) throws {
self.init()
let singleValueContainer = try decoder.singleValueContainer()
if singleValueContainer.decodeNil() == false {
let value = try singleValueContainer.decode(Value.self)
numeric = RealmOptional(value)
}
}
}
Using
@objc dynamic var numberOfStudents: RealmOptionalCodable<Int>?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With