XMLCoder
XMLCoder copied to clipboard
Class vs Struct for Decoding
Let's say I have this simple XML:
let sample = """
<wrapper>
<firstName>Peter</firstName>
<lastName>Ent</lastName>
<address>Atlanta, GA</address>
</wrapper>
"""
And I wish to decode it into the following Swift classes:
class MyBase: Codable {
var firstName: String?
var lastName: String?
}
class MyAddress: MyBase {
var address: String?
}
I've set up set up a function like this:
func decodeMessage<T: Codable>(_ data: Data) -> T? {
let response = try? XMLDecoder().decode(T.self, from: data)
return response
}
When I call it as follows:
let dataIn = sample.data(using: .utf8)!
if let person = parser.decodeMessage(dataIn) as MyAddress? {
print("Decode works: \(String(describing: person.firstName)) \(String(describing: person.lastName)) at \(String(describing: person.address))")
}
It only parses the MyBase class (firstName and lastName), ignoring address in the MyAddress class. Why is that?
I can work around this by using CodingKeys and implementing encode and decode functions, but I shouldn't have to, right?
Or am I missing something?
Unfortunately, you have to implement init(from decoder:) and encode functions manually when using classes with inheritance, this is how Codable works. This is also the case for JSONDecoder.
A workaround could be to use composition instead of inheritance:
struct Person: Codable {
let nameDetails: NameDetails
let address: Address
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
nameDetails = try container.decode(NameDetails.self)
address = try container.decode(Address.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(nameDetails)
try container.encode(address)
}
}
struct NameDetails: Codable {
var firstName: String?
var lastName: String?
}
struct Address: Codable {
var address: String?
}
let personJSON = #"{ "firstName": "Peter", "address": "Atlanta, Georgia, USA" }"#.data(using: .utf8)!
let addressJSON = #"{ "address": "London, United Kingdom" }"#.data(using: .utf8)!
let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: personJSON)
let address = try! decoder.decode(Address.self, from: addressJSON)
This way you'd only need to maintain custom Codable implementation only for the base Person type, while any fields added to NameDetails or Address would be handled automatically. And you could use either NameDetails or Address instead of Person if you need to parse just corresponding subsets of fields. I'm using JSON as an example here, but I would expect it to work the same with XMLCoder.
@peterent does that help with your issue?
Another option is to declare custom Codable conformance in your derived MyAddress class as shown in this SO answer. But you still have to tell the decoder that you're decoding MyAddress upfront, it won't be able to figure out whether to decode a base class or a derived class dynamically.
@MaxDesiatov Another question related to Class vs Struct. I've got decoding working with a class but when i change that class to a struct, the decoding no longer works. Why would that be?
It's very hard to say without looking at the actual code. Do you have an isolated reproducible test case for this?
I'll try to set something up.