-
Notifications
You must be signed in to change notification settings - Fork 116
Description
Hi,
I am trying to build a Decoder for a XML Schema that uses "inheritance" via the <xs:extension base="BaseClass"> schema element. Creating DTO model to match has me creating a base class and then deriving subclasses from it. Our schema is much more involved, but this sample illustrates the problem I'm having:
<SubclassType SubAttr1="sattr1" SubAttr2="sattr2" BaseAttr1="battr2" BaseAttr2="battr2" >
<SubElement1>selement1</SubElement1>
<SubElement2>selement2</SubElement2>
<BaseElement1>belement1</BaseElement1>
<BaseElement2>belement2</BaseElement2>
</SubclassType>The XML, attributes and elements, required and optional, are a combination of the base class and derived class in the XML schema. The attributes and elements defined in the base object are prefixed with "Base"; items defined in the subclass are prefixed with "Sub". To decode this XML, I created the following base class and subclass:
Base Class:
class BaseClass: Codable, DynamicNodeDecoding {
// attribute
public var baseAttr1: String // required
public var baseAttr2: String? // optional
// element
public var baseElement1: String // required
public var baseElement2: String? // optional
private enum CodingKeys : String, CodingKey {
case baseAttr1 = "BaseAttr1"
case baseAttr2 = "BaseAttr2"
case baseElement1 = "BaseElement1"
case baseElement2 = "BaseElement2"
}
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
switch key {
case CodingKeys.baseAttr1, CodingKeys.baseAttr2:
return .attribute
default:
return .element
}
}
}SubClass:
class SubClass: BaseClass {
// attribute
public var subAttr1: String = "<have-to-initialize-to-prevent-error>" // required
public var subAttr2: String? = nil // optional
// element
public var subElement1: String = "<have-to-initialize-to-prevent-error>" // required
public var subElement2: String? = nil // optional
private enum CodingKeys : String, CodingKey {
case subAttr1 = "SubAttr1"
case subAttr2 = "SubAttr2"
case subElement1 = "SubElement1"
case subElement2 = "SubElement2"
}
required init(from decoder: any Decoder) throws {
// Call superclass decoder initializer
try super.init(from: decoder)
// Decode and initialize all subclass properties first
let container = try decoder.container(keyedBy: CodingKeys.self)
// decode attributes
self.subAttr1 = try container.decode(String.self, forKey: .subAttr1) // required
self.subAttr2 = try container.decodeIfPresent(String.self, forKey: .subAttr2) // optional
// decode elements
self.subElement1 = try container.decode(String.self, forKey: .subElement1) // required
self.subElement2 = try container.decodeIfPresent(String.self, forKey: .subElement2) // optional
}
}The base class works as expected with the CodingKeys and DynamicNodeDecoding protocol. Attributes are decoded as attributes, elements as elements, and optionals handled with optional data types (?).
In the subclass, I understand that I cannot use DynamicNodeDecoding and, instead have to implement a init(from decoder: ...) constructor and manually decode the items. However, because I can't supply my own nodeDecoding(...) to specify the key type, .attribute vs .element, the attributes are being decodes as elements and failing:
Error decoding XML file: error=keyNotFound(CodingKeys(stringValue: "SubAttr1", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "SubAttr1", intValue: nil)], debugDescription: "No element found for key CodingKeys(stringValue: \"SubAttr1\", intValue: nil) (\"SubAttr1\").", underlyingError: nil))
I can see in XMLKeyedDecodingContainer.decodeConcrete(...) there is a switch to decode as attribute or element, but I do not know how I can override the strategy (attribute, element, or both) used for a particular item when I'm calling the container.decodeXXX(...) functions.
Can anyone suggest an approach that would allow me to decode attributes, required and optional, in a subclass? Thanks!
Note: This is a vast simplification of the XML schema I'm working with. Many subclasses use the base class and for each subclass, there is actually a hierarchy of 5-10 subclasses before you get to the class being decoded. In the end, if this is not doable with the current framework, I may need to flatten the schema into the DTOs (i.e.: no subclasses), but that will be no easy task either and would love to be able to represent the schema as-is in the DTOs.
Thanks!