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?
CodePudding user response:
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
}
CodePudding user response:
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