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.