How to encode nested objects into JSON (Swift)? - swift

I have such an object that should be encoded to JSON (My Playground example)
struct Toy: Codable {
var name: String
enum GiftKeys: String, CodingKey {
case toy = "name"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: GiftKeys.self)
try container.encode(self, forKey: .toy)
}
}
struct Employee: Encodable {
var name: String
var id: Int
var favoriteToy: Toy
enum CodingKeys: CodingKey {
case name, id
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(id, forKey: .id)
try favoriteToy.encode(to: encoder)
}
}
do {
let toy = Toy(name: "Teddy Bear")
let employee = Employee(name: "John Appleseed", id: 7, favoriteToy: toy)
let encoder = JSONEncoder()
let nestedData: Data = try encoder.encode(employee)
let nestedString = String(data: nestedData, encoding: .utf8)!
print(nestedString)
}
What I am going to achieve is that each object knows how to encode itself. So, in my example when I through the employee object to the encoder, so each of the objects (employee and Toy) is responsible for its encoding.
But when I try to run the example in the Playground looks like it comes to the stuck in the line try favoriteToy.encode(to: encoder)
What is the problem here?

This line is the problem:
try container.encode(self, forKey: .toy)
The toy is trying to encode itself, so this will call encode(to:) again, causing infinite recursion. You probably meant:
try container.encode(name, forKey: .toy)
Remember that in the encode(to:) method, you are trying to answer the question of "how to encode a Toy?". If you answer with "just encode the toy itself!" That's not really answering the question, is it?
This will print:
{"name":"Teddy Bear","id":7}
Notice that the employee's name is overwritten by the toy's name, since you encoded the toy's name using the same encoder and same key, as the employee's name.
That's probably not what you intended. You probably wanted:
enum CodingKeys: CodingKey {
case name, id, toy
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(id, forKey: .id)
try container.encode(favoriteToy, forKey: .toy)
}
// {"name":"John Appleseed","id":7,"toy":{"name":"Teddy Bear"}}
Note that all this code can be generated by the compiler, if you remove all your CodingKeys and encode methods:
struct Toy: Codable {
var name: String
}
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy
}

What I am going to achieve is that each object knows how to encode itself.
Each object does know to encode itself if you leave it alone by omitting the CodingKeys and encode(to methods
struct Toy: Encodable {
var name: String
}
struct Employee: Encodable {
var name: String
var id: Int
var favoriteToy: Toy
}
You made two serious mistakes. If you implement encode(to you have to encode each struct member for the corresponding CodingKey rather than encoding self or calling encode(to: on a struct member.
So replace
try container.encode(self, forKey: .toy)
with
try container.encode(name, forKey: .toy)
and replace
try favoriteToy.encode(to: encoder)
with
try container.encode(favoriteToy, forKey: .favoriteToy)
and add also the missing CodingKey favoriteToy and the raw value type
private enum CodingKeys: String, CodingKey {
case name, id, favoriteToy
}
But in this case the best solution is not to implement encode(to

Related

How do I get automatic Encodable conformance with array property but without using generics? [duplicate]

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).

Swift custom decodable initializer without CodingKeys

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

Errors when using Codable - Swift

I am using Codable to try to Encode JSON to a Model but I get two errors.
Value of type 'KeyedEncodingContainer' has no member 'encoder'
Here's my code:
import UIKit
struct NewCustomer : Codable {
var firstName :String
var lastName :String
private enum CodingKeys : String, CodingKey {
case firstName
case lastName
}
func encode(to encoder :Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encoder(self.firstName, forKey: .firstName) // error here
try container.encoder(self.lastName, forKey: .lastName) // error here
}
}
let customer = NewCustomer(firstName: "Jake", lastName: "Reynolds")
let encodedCustomerJSON = try!
JSONEncoder().encode(customer)
print(encodedCustomerJSON)
print(String(data: encodedCustomerJSON, encoding: .utf8)!)
Change encoder to encode on the two lines giving errors. Please note that the line above (i.e. var container...) will keep encoder.
try container.encode(self.firstName, forKey: .firstName)
try container.encode(self.lastName, forKey: .lastName)
As already mentioned it's a typo encode vs. encoder:
try container.encode(...
Practically you don't need to specify CodingKeys and the encoding method at all in this case, this is sufficient:
struct NewCustomer : Codable {
var firstName, lastName : String
}

Merge Encodable in Swift

I have the following Swift structs
struct Session: Encodable {
let sessionId: String
}
struct Person: Encodable {
let name: String
let age: Int
}
let person = Person(name: "Jan", age: 36)
let session = Session(sessionId: "xyz")
that I need to encode to a json object that has this format:
{
"name": "Jan",
"age": 36,
"sessionId": "xyz"
}
where all keys of the Session are merged into the keys of the Person
I thought about using a container struct with a custom Encodable implementation where I use a SingleValueEncodingContainer but it can obviously encode only one value
struct RequestModel: Encodable {
let session: Session
let person: Person
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(person)
// crash
try container.encode(session)
}
}
let person = Person(name: "Jan", age: 36)
let session = Session(sessionId: "xyz")
let requestModel = RequestModel(session: session, person: person)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(requestModel)
let json = String(data: data, encoding: .utf8)!
print(json)
I cannot change the json format as it's a fixed network API. I could have the sessionId as property of the Person but I'd like to avoid that as they are unrelated models.
Another way could be to have the RequestModel copy all the properties from the Session and Person as follows but it's not very nice as my real structs have much more properties.
struct RequestModel: Encodable {
let sessionId: String
let name: String
let age: Int
init(session: Session, person: Person) {
sessionId = session.sessionId
name = person.name
age = person.age
}
}
Call encode(to:) of each encodable objects, instead of singleValueContainer().
It makes possible to join multi encodable objects into one encodable object without defining extra CodingKeys.
struct RequestModel: Encodable {
let session: Session
let person: Person
public func encode(to encoder: Encoder) throws {
try session.encode(to: encoder)
try person.encode(to: encoder)
}
}
Use encoder.container(keyedBy: CodingKeys.self) instead of singleValueContainer() and add the key-value pairs separately, i.e.
struct RequestModel: Encodable
{
let session: Session
let person: Person
enum CodingKeys: String, CodingKey {
case sessionId, name, age
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(person.age, forKey: RequestModel.CodingKeys.age)
try container.encode(person.name, forKey: RequestModel.CodingKeys.name)
try container.encode(session.sessionId, forKey: RequestModel.CodingKeys.sessionId)
}
}
Output:
{
"age" : 36,
"name" : "Jan",
"sessionId" : "xyz"
}
Let me know in case you still face any issues.
I'd like to expand on #marty-suzuki's answer here, because there are a few nuances that may be missed if you're not careful. Here's my version of the code:
struct EncodableCombiner: Encodable {
let subelements: [Encodable]
func encode(to encoder: Encoder) throws {
for element in subelements {
try element.encode(to: encoder)
}
}
}
Simply instantiate with an array of codable objects and treat the resulting object as a codable object in its own right. Now, there are a couple of important notes to remember when using this method:
You can only have one type of root object in your JSON, which will either be a single value, an array, or a dictionary. So when you implement encode(to:) in your various codable objects, never create your container using encoder.singleValueContainer.
Every object you wish to combine must operate on the same kind of container, so if one of them uses unkeyedContainer(), so must they all. Similarly, if one uses container(keyedBy:) then the others must too.
If you're using keyed containers, then no two variables across all the combined objects can share the same key name! Otherwise, you're going to find that they overwrite each other since they're being parsed into the same dictionary.
An alternative that would alleviate these problems, but does not result in the same JSON structure, would be this:
struct EncodableCombiner: Encodable {
let elementA: MyEncodableA
let elementB: MyEncodableB
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(elementA)
try container.encode(elementB)
}
}
Now, this is slightly less convenient because we can't simply supply an array of objects that conform to Encodable; it needs to know exactly what they are to call container.encode(). The result is a JSON object with an array as its root object, and each subelement expressed as an element in that array. In fact, you could simplify it further like this:
struct EncodableCombiner: Encodable {
let elementA: MyEncodableA
let elementB: MyEncodableB
}
... which would of course result in a dictionary root object with the encoded form of MyEncodableA keyed as elementA, and MyEncodableB as elementB.
It all depends on what structure you want.

How to make the RealmSwift RealmOptional compatible with Swift Codable?

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?
}