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.
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>?
Add #objcMembers above your Realm Model class.
Use variable as below
public dynamic var someValue = RealmOptional<Int>()
While assigning values to realm optional, you can use someValue.value = 10
By default someValue will be nil.
I found this solution and it works like a charm. I am using the updated code from srv7's comment.
Since last year, Realm added a new and more easier way for optionals, using #Persisted - docs
How to use it:
class Order: Object, Codable {
#Persisted(primaryKey: true) var productOrderId: Int?
#Persisted var name: String?
#Persisted var standardPrice: Double?
#Persisted var paid: Bool?
}
Related
There are a number of questions on this topic but I haven't figured out why my solution doesn't work yet.
I have some protocol
protocol Foo: Decodable {
var prop1: String? { get set }
var prop2: Bool? { get set }
var prop3: Int? { get set }
init()
}
enum FooCodingKeys: CodingKey { case prop1, prop2, prop3 }
extension Foo {
init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: FooCodingKeys.self)
self.prop1 = try container.decode(String?, forKey: .prop1)
self.prop2 = try container.decode(Bool?, forKey: .prop2)
self.prop3 = try container.decode(Int?, forKey: .prop3)
}
}
Technically, this now has a default implementation which should make the whole protocol perfectly decodable. And the compiler doesn't complain about this at all. So now in a struct, If I have
enum BarCodingKeys: CodingKey { case foos }
struct Bar: Decodable {
var foos: [Foo]
init(from decoder: Decoder) {
let container = try decoder.container(keyedBy: BarCodingKeys.self)
self.foos = try container.decode([Foo].self, forKey: .prop1)
}
}
Then I get the error Protocol 'Foo' as a type cannot conform to 'Decodable'.
Is there a way for me to make protocol's conform to Codable using extensions? and if not, why?
I'm not sure what your use case is, so what I propose may not fit, but easiest way out is to tell compiler that you are decoding a concrete type, not a protocol. But that concrete type implements Foo. So you change Bar like this:
struct Bar<T: Foo>: Decodable {
var foos: [T]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: BarCodingKeys.self)
self.foos = try container.decode([T].self, forKey: .foos)
}
}
So now you are decoding [T].self - a concrete type, not a protocol. Of course the drawback is that you have to provide a type when you are decoding the class itself, i.e. you cannot say:
JSONDecoder().decode(Bar.self, from: jsonData)
You have to provide a type here:
struct Foo1: Foo {
var prop1: String?
var prop2: Bool?
var prop3: Int?
}
let a = try JSONDecoder().decode(Bar<Foo1>.self, from: jsonData)
However, as you see Foo1 does not need to implement init(from decoder: Decoder) throws, the one from protocol is correctly used.
Additional note: as you may noticed I changed forKey: .prop1 to forKey: .foos, because based on your code you expect an object with property foos, which as a value contains an array of objects that match protocol Foo, something like this:
{ "foos": [
{ "prop1": ...,
"prop2": ...,
"prop3": ...
},
{ "prop1": ...,
"prop2": ...,
"prop3": ...
},
...
]
}
If this is not the case, please provide an example of JSON you are trying to decode.
And also you need to fix this function (use decodeIfPresent instead of optional):
extension Foo {
init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: FooCodingKeys.self)
self.prop1 = try container.decodeIfPresent(String.self, forKey: .prop1)
self.prop2 = try container.decodeIfPresent(Bool.self, forKey: .prop2)
self.prop3 = try container.decodeIfPresent(Int.self, forKey: .prop3)
}
}
First, I think there's a key misunderstanding here:
protocol Foo: Decodable { ... }
In your subject, you suggest this is "conforming a protocol to Codable," but that's not what this does. This says "in order to conform to Foo, a type must first conform to Decodable." Foo absolutely does not itself conform to Codable. Then you note:
Technically, this now has a default implementation which should make the whole protocol perfectly decodable.
This absolutely is not true in either direction. Consider a simple example:
struct ConcreteFoo: Foo {
var prop1: String?
var prop2: Bool?
var prop3: Int?
var anotherPropNotInFoo: CBPeripheral
}
How does your init(from:) decode ConcreteFoo? What value is applied to anotherPropNotInFoo?
In the other direction, consider three conforming types:
struct FooA: Foo {
var prop1: String?
var prop2: Bool?
var prop3: Int?
var anotherProp: Bool = true
}
struct FooA: Foo {
var prop1: String?
var prop2: Bool?
var prop3: Int?
}
struct FooC: Foo {
var prop1: String?
var prop2: Bool?
var prop3: Int?
var anotherProp: Bool = true
func doC() { print("I'm a C!") }
}
Say your JSON were:
[
{},
{ "anotherProp": false }
]
Now consider this code:
let bar = try JSONDecoder().decode(Bar.self, from: json)
for foo in bar.foos {
print("\(type(of: foo)")
if let c = foo as? FooC {
c.cThing()
}
}
What should happen? What actual type should these be? Remember there are an unbounded number of other implementations of Foo that might exist (including in other modules). There's no way for this to work. If you mean that Foo only has precisely these properties, no others, and no other methods, it's not a protocol. It's just a struct. If you mean there to be an unbounded number of types that implement it, then there's no way to determine what they are.
I'm trying to create a Codable struct with an [any Protocol] in Swift 5.7
struct Account: Codable {
var id: String
var name: String
var wallets: [any Wallet]
}
protocol Wallet: Codable {
var id: String { get set }
//... other codable properties
}
However, I'm getting these errors even though Wallet conforms to Codable
Type 'Account' does not conform to protocol 'Decodable'
Type 'Account' does not conform to protocol 'Encodable'
Is it possible to make an any conform to Codable?
or is this still not possible with Swift 5.7?
EDIT: As answered, you have to implement your own conformance. This is how I did mine:
protocol Wallet: Identifiable, Codable {
var id: String { get set }
}
struct DigitalWallet: Wallet {
var id: String
// other properties
}
struct CashWallet: Wallet {
var id: String
// other properties
}
struct Account {
var id: String
var name: String
var wallets: [any Wallet]
}
extension Account: Codable {
enum CodingKeys: String, CodingKey {
case id
case name
case digitalWallet
case cashWallet
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
name = try values.decode(String.self, forKey: .name)
let dW = try values.decode([DigitalWallet].self, forKey: .digitalWallet)
let cW = try values.decode([CashWallet].self, forKey: .cashWallet)
wallets = []
wallets.append(contentsOf: dW)
wallets.append(contentsOf: cW)
}
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)
var digitalWallet: [DigitalWallet] = []
var cashWallet: [CashWallet] = []
wallets.forEach { wallet in
if wallet is DigitalWallet {
digitalWallet.append(wallet as! DigitalWallet)
} else if wallet is CashWallet {
cashWallet.append(wallet as! CashWallet)
}
}
try container.encode(digitalWallet, forKey: .digitalWallet)
try container.encode(cashWallet, forKey: .cashWallet)
}
}
But I have reverted to just using this instead:
struct Account: Codable {
var id: String
var name: String
var cashWallets: [CashWallet]
var digitalWallets: [DigitalWallet]
}
I worry the performance overhead of any Protocol isn't worth it just to comply to the Dependency Inversion Principle.
Protocols do not conform to themselves, so any Wallet is not itself Codable, and so you won't get an automatic conformance here, and you won't get automatic handling of arrays. But you can handle it yourself without trouble:
extension Account: Encodable {
enum CodingKeys: CodingKey {
case id
case name
case wallets
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.name, forKey: .name)
var walletContainer = container.nestedUnkeyedContainer(forKey: .wallets)
for wallet in wallets {
try walletContainer.encode(wallet)
}
}
}
If you comment-out wallets temporarily, Xcode 14 will auto-complete the rest of the encode(to:) method for you, so you only need to write the loop for wallets.
This is something that may improve in the future. There's no deep reason that Swift can't auto-generate this conformance. It just doesn't today.
However, as Alexander notes, it is not in possible to conform to Decodable in a general way because it requires an init. You have to know which type you want to decode, and the serialized data doesn't include that information (at least the way you've described it). So you'll have to make choices that you hand-write (for example, using some default Wallet type).
Let's say I have the following decodable struct as an example illustrating what I'm trying to do:
struct Object: Decodable {
var id: String
var name: String
}
and this JSON:
[
{
"id": "a",
"name": "test"
},
{
"id": "b",
"name": null
}
]
Notice that the name property can be null sometimes. This would mostly work fine like it is since the json keys match the struct property names, so I don't need a CodingKey enum, but the name property can be null sometimes. However, instead of making name optional, I want to substitute a default string, so I need a custom initializer:
struct Object: Decodable {
var id: String
var name: String
init(from decoder: Decoder) throws {
...
self.name = <value from decoder> ?? "default name"
...
}
}
But this requires a CodingKey object. I'm using the default keys. Do I need a CodingKey enum as well now? Even though all my keys match up? Or is there a way to have a custom Decodable initializer using just the keys as they are?
Is there maybe some sort of default container I can use?
let container = try decoder.container(keyedBy: <defaultContainer???>)
I tried using both of these variants, but neither worked:
let container = try decoder.singleValueContainer()
let container = try decoder.unkeyedContainer()
How can I have a custom Decodable intializer but also use the default keys?
The issue is that CodingKeys is only automatically generated for you if didn’t fully manually conform to the relevant protocols. (This is very familiar for Objective-C developers, where a property’s backing ivar would not be automatically synthesized if you manually implemented all the relevant accessor methods.)
So, in the following scenarios, the CodingKeys is not created automatically for you:
You adopt only Decodable and implemented your own init(from:);
You adopt only Encodable and implemented your own encode(to:); or
You adopt both Encodable and Decodable (or just to Codable) and implemented your own init(from:) and encode(to:).
So your case falls within the first scenario, above.
It has been suggested that you can get around your conundrum by adopting Codable, even though you’ve only implemented init(from:) and presumably don’t plan on ever using the Encodable behavior. In effect, that is relying upon a side effect of a protocol you don’t plan on really using.
It doesn’t really matter too much, and adopting Codable works, but it might be considered to be “more correct” to go ahead and define your CodingKeys, rather than relying on the unimplemented Encodable side-effect:
struct Object: Decodable {
var id: String
var name: String
enum CodingKeys: String, CodingKey {
case id, name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = (try? container.decode(String.self, forKey: .name)) ?? "Default Value"
}
}
The auto-generation for CodingKeys is really weird. The scope and availability of it changes based on what members you have.
Say you just have a Decodable. These compile:
struct Decodable: Swift.Decodable {
static var codingKeysType: CodingKeys.Type { CodingKeys.self }
}
struct Decodable: Swift.Decodable {
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}
…and you can put them together, if you add private.
struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}
…But make that func an initializer, and again, no compilation.
struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}
You can change it to be fully Codable, not just Decodable…
struct Decodable: Codable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}
But then you can't use CodingKeys at type scope, so the property won't compile.
Considering you probably don't need such a property, just use Codable, file a bug with Apple referencing this answer, and hopefully we can all switch to Decodable when they fix it. 😺
You can emulate the behavior using property wrappers but it's not the perfect solution and it's a bit hacky. The problem has been discussed on Swift forums multiple times already.
With a property wrapper:
struct Object: Decodable {
var id: String
#DecodableDefault var name: String
}
Code for the property wrapper:
public protocol DecodableDefaultValue: Decodable {
static var defaultDecodableValue: Self { get }
}
#propertyWrapper
public struct DecodableDefault<T: Decodable>: Decodable {
public var wrappedValue: T
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(T.self)
}
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
}
extension DecodableDefault: Encodable where T: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
extension DecodableDefault: Equatable where T: Equatable { }
extension DecodableDefault: Hashable where T: Hashable { }
public extension KeyedDecodingContainer {
func decode<T: DecodableDefaultValue>(_: DecodableDefault<T>.Type, forKey key: Key) throws -> DecodableDefault<T> {
guard let value = try decodeIfPresent(DecodableDefault<T>.self, forKey: key) else {
return DecodableDefault(wrappedValue: T.defaultDecodableValue)
}
return value
}
}
extension Array: DecodableDefaultValue where Element: Decodable {
public static var defaultDecodableValue: [Element] {
return []
}
}
extension Dictionary: DecodableDefaultValue where Key: Decodable, Value: Decodable {
public static var defaultDecodableValue: [Key: Value] {
return [:]
}
}
extension String: DecodableDefaultValue {
public static let defaultDecodableValue: String = ""
}
extension Int: DecodableDefaultValue {
public static let defaultDecodableValue: Int = 0
}
To list a few problems:
you cannot select the default value (can be done differently but it's more complicated)
if you want to use let, you need a separate immutable wrapper.
As a comment made on your question says the compiler generates a CodingKeys object for you. You can implement a custom enum when the keys mismatch names on your enum or class respecting the JSON data you're receiving from the source.
You can implement your object this way:
struct TestObject: Codable {
var id: String
var name: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let nameOrNil = try? container.decode(String.self, forKey: .name)
self.id = id
self.name = nameOrNil ?? "Default value"
}
}
The container's decode(_:forKey:) method can throw and error but if you make the implementation to discharge the error and instead returning an optional value with try? you can apply a nil coalescing operator to assign your default value whenever the name is not included on the JSON.
Proof here:
let json = """
[
{
"id": "a",
"name": "test"
},
{
"id": "b",
"name": null
}
]
""".data(using: .utf8)!
let decodedArray = try JSONDecoder().decode([TestObject].self, from: json)
print(decodedArray)
References:
https://medium.com/swiftly-swift/swift-4-decodable-beyond-the-basics-990cc48b7375
?? operator in Swift
https://developer.apple.com/documentation/swift/decodable
I have a service whose response vary based on the orderNumer(input parameter which I pass to service). all most all object are same but only one object(ex meta object which is dictionary)vary based on order number. But I would like to reuse the same model class everywhere but due to different meta object can't be able to create that meta object in model class.I can achieve it by creating individual model class but not a right solution.
struct BookingInfo: Codable {
let created_time: Int
// Some other 20 key value pairs
let oms_meta: Meta /* oms_meta": {
"package": {
"description": "gzhdjjdjd"
}*/
}
// Meta for Order1
struct Meta: Codable {
let package: Description
enum CodingKeys : String, CodingKey {
case package = "package"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
package = try values.decode(Description.self, forKey: .package)
}
}
// Meta for Order 2
struct Meta: Codable {
let customer_payment_details: Int
let items: Int // this can be anything dictinary or some time array of dictionary
enum CodingKeys : String, CodingKey {
case package = "customer_payment_details"
case items = "items"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
customer_payment_details = try values.decode(Int.self, forKey: .package)
items = try values.decode(Int.self, forKey: .package)
}
}
Only meta parameters are varying from service to service.
Thanks in advance
If you want to only use 1 model, you should provide a variable for all the possible keys in the dictionary and set them as optional.
Let's say all orders have an orderNumber, but only some have an orderImage you could do it like this:
struct Order: Codable {
var orderNumber: Int
var orderImage: Data?
var orderName: String?
var orderDescription: String?
}
This way, the returned data only has to contain an orderNumber to be able to decode it.
Edit: You'd have to make a seperate model for each type of 'meta data' as they seem to be very different from eachother. Note that there is nothing wrong with creating a lot of model classes as long as they represent a specific data object.
[
{
"created_time": 1,
"oms_meta": {
"name": "1"
}
},
{
"created_time": 1,
"oms_meta": [
1
]
}
]
Suppose we have a response something like this, where you can have multiple oms_meta objects. So what we can do here is to specify an enum which will contain both array as well as dictionary, it doesn't matter if the service sends you array or dictionary if you need to specify other data types mentioned it in that enum. So it will be like,
struct BookingInfoElement: Codable {
let createdTime: Int?
let omsMeta: OmsMetaUnion?
enum CodingKeys: String, CodingKey {
case createdTime = "created_time"
case omsMeta = "oms_meta"
}
}
enum OmsMetaUnion: Codable {
case integerArray([Int])
case omsMetaClass(OmsMetaClass)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([Int].self) {
self = .integerArray(x)
return
}
if let x = try? container.decode(OmsMetaClass.self) {
self = .omsMetaClass(x)
return
}
throw DecodingError.typeMismatch(OmsMetaUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for OmsMetaUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integerArray(let x):
try container.encode(x)
case .omsMetaClass(let x):
try container.encode(x)
}
}
}
struct OmsMetaClass: Codable {
let name: String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
}
typealias BookingInfo = [BookingInfoElement]
So, if you specifically want to have multiple types for a single variable you can create another enum with that multiple types and assign accordingly. You can access that variable using switch like, and check on array and dictionary types.
After doing some analyses I found the solution for my requirement. As I wanted to reuse the Model class and OMS_Meta dictionary different from product to product, I'm adding omsMeta to JSONDecoder().userInfo property
struct BookingInfo: Codable {
let created_time: Int
let oms_meta: [String: Any]
}
public static let metaUserInfo = CodingUserInfoKey(rawValue: "oms_meta")!
decoder.userInfo[BookingInfo.metaUserInfo] = jsonDecode
By doing this I can re use my model class.
I've been using RealmSwift and Codable for a long time in a project, however, my api dev just gave me two new properties that only exist on some return calls of an object. If I call getUserInfo, I receive a user model without these two properties. In such an instance, you would use decodeIfPresent in codable and set the data type to optional. However, these two fields are epoch time values, making them some sort of number. Realm requires your datatypes to be prefixed with #objc.
#objc dynamic var scanIn:Double = 0
Of course, all number primitives work like this, but NONE of them work as optionals. You must use NSNumber or similar to use optionals with ObjC, but lucky me, Codable doesn't work with NSNumber. I know I have a lot of different options here, but I was really looking for something simple and quick that wouldn't require me to rebuild the entire model with mapping or convert everything when I receive it. I'll be writing that out a workaround for now, but I would really like to keep things simple and clean.
I tried setting a value if none return and just using a non-optional type like this
scanIn = try container.decodeIfPresent(Double.self, forKey:.scanIn) ?? 0
However, this sets ALL values to 0 for some reason. I have no idea why it does that, but another dev has advised that it doesn't work in codable like that and I had to set the double to optional. I would like to clarify that this number exists immediately before conversion, but is 0 after.
Any ideas on an easy fix? Maybe I'm doing something wrong?
You could use the RealmOptional<Double> type.
As stated in the documentation:
Optional numeric types are declared using the RealmOptional type:
let age = RealmOptional<Int>()
RealmOptional supports Int, Float, Double, Bool, and all of the sized versions of Int (Int8, Int16, Int32, Int64).
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
}
}
}