Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decoding attributes in subclass #287

Open
mcmap4 opened this issue Nov 4, 2024 · 0 comments
Open

Decoding attributes in subclass #287

mcmap4 opened this issue Nov 4, 2024 · 0 comments

Comments

@mcmap4
Copy link

mcmap4 commented Nov 4, 2024

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant