Home > database >  How to encode nested objects into JSON (Swift)?
How to encode nested objects into JSON (Swift)?

Time:12-24

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

  • Related