iosiphoneswiftxmlxmlmapper

Swift XMLMapper decoding nested attributes


Using the library from here. Here are the details,

XML:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <status code="25">Verification required</status>
    <parsed-challenge>
        <action type-id="11">
            <url content-type="application/x-www-form-urlencoded" method="POST" type-id="1">https://example.com</url>
            <hidden-fields>
                <apiRequest>MIAGCSqGSIb3DQEHA6CAMIACAQAxggFAMIIBPAIBAD</apiRequest>
            </hidden-fields>
        </action>
    </parsed-challenge>
    <return-url>https://example.com</return-url>
</root>

Structure:

struct Secure: XMLMappable {

    internal(set) var statusCode: Int?
    internal(set) var status: String?
    internal(set) var actionType: Int?
    internal(set) var url: URLInfo?
    internal(set) var hiddenFields: [String:String]?
    internal(set) var returnURL: Foundation.URL?

    public var nodeName: String!

    public init(map: XMLMap) {
        statusCode = map.value()
        status = map.value()
        actionType = map.value()
        url = map.value()
        hiddenFields = map.value()
        returnURL = map.value()
    }

    public mutating func mapping(map: XMLMap) {
        statusCode      <- map["status"].attributes["code"]
        status          <- map["status"].innerText
        actionType      <- map["parsed-challenge.action"].attributes["type-id"]
        url             <- map["parsed-challenge.action.url"]
        hiddenFields    <- map["parsed-challenge.action.hidden-fields"]
        returnURL       <- (map["return-url"], XMLURLTransform())
    }
}

On decoding,

Secure(statusCode: nil, status: nil, actionType: nil, url: Optional(URLInfo(url: Optional(https://example.com), method: Optional("POST"), contentType: Optional("application/x-www-form-urlencoded"), typeId: Optional(1))), hiddenFields: Optional(["__name": "hidden-fields", "apiRequest": "MIAGCSqGSIb3DQEHA6CAMIACAQAxggFAMIIBPAIBAD"]), returnURL: Optional(https://example.com))

What is wrong with status, statusCode and actionType? Why are they not decoding, is it because deeply nested decoding is not possible?


Solution

  • I would love my library to be able to work like that. (the map of nested values are pretty clean that way) But currently it is hard to map nested attributes and innerText of elements that have other elements or attributes inside.

    So, the suggested model to map your XML is something like this:

    struct Secure: XMLMappable {
        public var nodeName: String!
    
        internal(set) var status: Status?
        internal(set) var action: Action?
        internal(set) var returnURL: Foundation.URL?
    
        public init(map: XMLMap) { }
    
        public mutating func mapping(map: XMLMap) {
            status       <- map["status"]
            action       <- map["parsed-challenge.action"]
            returnURL    <- (map["return-url"], XMLURLTransform())
        }
    }
    
    struct Status: XMLMappable {
        public var nodeName: String!
    
        internal(set) var statusCode: Int?
        internal(set) var message: String?
    
        public init(map: XMLMap) { }
    
        public mutating func mapping(map: XMLMap) {
            statusCode    <- map.attributes["code"]
            message       <- map.innerText
        }
    }
    
    struct Action: XMLMappable {
        public var nodeName: String!
    
        internal(set) var actionType: Int?
        internal(set) var url: URLInfo?
        internal(set) var hiddenFields: [String:String]?
    
        public init(map: XMLMap) { }
    
        public mutating func mapping(map: XMLMap) {
            actionType      <- map.attributes["type-id"]
            url             <- map["url"]
            hiddenFields    <- map["hidden-fields"]
        }
    }
    
    struct URLInfo: XMLMappable {
        public var nodeName: String!
    
        internal(set) var contentType: String?
        internal(set) var method: String?
        internal(set) var typeID: Int?
        internal(set) var url: Foundation.URL?
    
        public init(map: XMLMap) { }
    
        public mutating func mapping(map: XMLMap) {
            contentType    <- map.attributes["content-type"]
            method         <- map.attributes["method"]
            typeID         <- map.attributes["type-id"]
            url            <- (map.innerText, XMLURLTransform())
        }
    }
    

    For my personal use though (because I know how to hack my library) I could use something like this:

    struct Secure: XMLMappable {
        public var nodeName: String!
    
        internal(set) var statusCode: Int?
        internal(set) var status: String?
        internal(set) var actionType: Int?
        internal(set) var url: URLInfo?
        internal(set) var hiddenFields: [String:String]?
        internal(set) var returnURL: Foundation.URL?
    
        public init(map: XMLMap) { }
    
        public mutating func mapping(map: XMLMap) {
            statusCode      <- map["status._code"]
            status          <- map["status.__text"]
            actionType      <- map["parsed-challenge.action._type-id"]
            url             <- map["parsed-challenge.action.url"]
            hiddenFields    <- map["parsed-challenge.action.hidden-fields"]
            returnURL       <- (map["return-url"], XMLURLTransform())
        }
    }
    
    struct URLInfo: XMLMappable {
        public var nodeName: String!
    
        internal(set) var contentType: String?
        internal(set) var method: String?
        internal(set) var typeID: Int?
        internal(set) var url: Foundation.URL?
    
        public init(map: XMLMap) { }
    
        public mutating func mapping(map: XMLMap) {
            contentType    <- map.attributes["content-type"]
            method         <- map.attributes["method"]
            typeID         <- map.attributes["type-id"]
            url            <- (map.innerText, XMLURLTransform())
        }
    }
    

    Both models will work ok.

    I will definitely update this post, if nested mapping improved in next versions.