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.
Related
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).
I have the following Codable protocol containing a variable which I would like to exclude from the codable ones.
Problem is that I can't use the CodingKeys enum made for that within my own protocol: Type 'CodingKeys' cannot be nested in protocol 'Animal'.
protocol Animal: Codable {
var name: String { get set }
var color: String { get }
var selfiePicture: Selfie { get }
// Not possible
enum CodingKeys: String, CodingKey {
case name
case color
}
}
How could I resolve this?
EDIT with more code and more specific example
Animal is used by several structs (can't be classes):
struct Frog: Animal {
var name: String
var color: String
// extra variables on top of Animal's ones
var isPoisonous: Bool
var selfiePicture = [...]
}
It is also used as a variable array on another top-codable object:
final class Farm: Codable {
var address: String
// more variables
var animals: [Animal]
enum CodingKeys: String, CodingKey {
case address
case animals
}
convenience init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
address = try values.decode(String.self, forKey: .address)
animals = try values.decode([Animal].self, forKey: .animals) // ERROR --> Protocol 'Animal' as a type cannot conform to 'Decodable'
}
}
One way to solve this is to use composition, move the common properties to a new type and use that type in the protocol.
So let's make a type for the common properties and let that type hold the CodingKey enum
struct AnimalCommon: Codable {
var name: String
var color: String
var selfiePicture: Selfie = Selfie()
enum CodingKeys: String, CodingKey {
case name
case color
}
}
And the protocol becomes
protocol Animal: Codable {
var common: AnimalCommon { get set }
}
After that it will be quite easy to implement the actual Animal types, for example
struct Frog: Animal {
var common: AnimalCommon
var isPoisonous: Bool
}
let frog = Frog(common: AnimalCommon(name: "kermit", color: "green"), isPoisonous: false)
do {
let data = try JSONEncoder().encode(frog)
if let string = String(data: data, encoding: .utf8) { print(frog) }
} catch {
print(error)
}
You can also add an extension to the protocol with computed properties so you can access the properties directly, i.e frog.name = "Kermit"
extension Animal {
var name: String {
get {
common.name
}
set {
common.name = newValue
}
}
var color: String {
common.color
}
}
Following Protocol type cannot conform to protocol because only concrete types can conform to protocols, I had to give up on the protocol and use a struct + enum inside instead.
Even though #JoakimDanielson's answer was promising, it does not fix an error I have while trying to decode the Animal array from my Farm class: Protocol 'Animal' as a type cannot conform to 'Decodable'.
Here is how my model looks like at the end:
struct Animal: Codable {
enum Species: Codable {
case frog(FrogSpecificities)
case ...
var selfiePicture: Selfie {
switch self {
case .frog(let frogSpecificities):
return frogSpecificities.selfie
case ...
...
}
}
enum CodingKeys: String, CodingKey {
case FrogSpecificities
case ...
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let frogSpecificities = try? values.decode(FrogSpecificities.self, forKey: .frogSpecificities) {
self = .frog(frogSpecificities)
} else if ...
...
} else {
// throw an error here if no case was decodable
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .frog(let frogSpecificities):
try container.encode(frogSpecificities, forKey: .frogSpecificities)
case ...:
...
}
}
}
var name: String
let color: String
var species: Species
enum CodingKeys: String, CodingKey {
case name
case color
case specificities
}
}
struct FrogSpecificities: Codable {
let isPoisonous: Bool
let selfie = Selfie()
enum CodingKeys: String, CodingKey {
case isPoisonous
}
}
final class Farm: Codable {
var address: String
var animals: [Animal]
enum CodingKeys: String, CodingKey {
case address
case animals
}
convenience init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
address = try values.decode(String.self, forKey: .address)
animals = try values.decode([Animal].self, forKey: .animals) // Works fine
}
}
My Farm object can now contains an Animal arrays with specific codable struct for each one of them. It can also contains variables which are not codable.
I can access each specificities of my animals like this:
if let firstAnimel = MyFarm.animals.firstObject,
case .frog(let frogSpecificities) = firstAnimal.species {
print(frogSpecificities.isPoisonous)
}
I am trying to decode an array of items logicBlocks that conform to a custom protocol LogicBlock.
struct MyModel: Codable {
var var1: String
var var2: Int
var logicBlocks: [LogicBlock]
}
I've looked at this question which asks how to decode an array of items, however I have a protocol which contains an enum like this:
enum LogicBlockTypeID: Int, Codable {
case a = 500
case b = 501
case c = 502
}
protocol LogicBlock: Codable {
var typeID: LogicBlockTypeID { get }
}
and structs implement LogicBlock like this:
struct Struct1: LogicBlock {
var someVar = 32
var typeID = .a
}
struct Struct2: LogicBlock {
var someString = "hello"
var typeID = .b
}
struct Struct2: LogicBlock {
var typeID = .c
}
I have attempted to decode MyModel by using init(from decoder: Decoder)
var codeContainer = try values.nestedUnkeyedContainer(forKey: .code)
var parsedCode = [LogicBlock]()
enum codingKeys: String, CodingKey {
...
case .logicBlocks
}
init(from decoder: Decoder) {
...
var codeContainer = try values.nestedUnkeyedContainer(forKey: .code)
var parsedCode = [LogicBlock]()
while !codeContainer.isAtEnd {
let nestedDecoder = try codeContainer.superDecoder()
let block = try Program.decodeLogicBlock(from: nestedDecoder)
parsedCode.append(block)
}
}
private static func decodeLogicBlock(from decoder: Decoder) throws -> LogicBlock {
let values = try decoder.container(keyedBy: LogicBlockTypeID.self)
// SOMETHING TO DISTINGUISH WHAT TYPE WE HAVE
return ...
}
How are we able to know the type of object that we are decoding here?
You can implement a concrete type, i.e AnyLogicBlock that would expose two variables let typeID: LogicBlockTypeID and let wrapped: LogicBlock
Here is a simplified example:
enum TypeID: String, Codable {
case a, b
}
protocol MyType {
var type: TypeID { get }
}
struct MyConreteTypeA: MyType {
var type: TypeID
var someVar: Int
}
struct MyConreteTypeB: MyType {
var type: TypeID
var someString: String
}
struct AnyType: MyType, Decodable {
private enum CodingKeys: String, CodingKey {
case type, someVar, someString
}
let type: TypeID
let wrapped: MyType
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(TypeID.self, forKey: .type)
switch type {
case .a:
wrapped = MyConreteTypeA(type: type, someVar: try container.decode(Int.self, forKey: .someVar))
case .b:
wrapped = MyConreteTypeB(type: type, someString: try container.decode(String.self, forKey: .someString))
}
}
}
var json = #"""
[
{ "type": "a", "someVar": 1 },
{ "type": "b", "someString": "test" }
]
"""#
let result = try! JSONDecoder().decode([AnyType].self, from: json.data(using: .utf8)!)
for item in result {
switch item.type {
case .a:
let casted = item.wrapped as! MyConreteTypeA
print("MyConreteTypeA: \(casted)")
case .b:
let casted = item.wrapped as! MyConreteTypeB
print("MyConreteTypeB: \(casted)")
}
}
Alternatively you can implement Decodable for concrete types and delegate decoding to them after determining the expected type based on type ID property.
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
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?
}