Swift XMLMapper decoding nested attributes - ios

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?

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.

Related

No response from URL in XML parsing - Alamofire Swift 5

I am using XMLMapper with AlamoFire request to get response. URL is working fine on browsers but when I try to use in swift it skips the function. Even I cannot debug the response. Its before parsing, not getting any data or response from the URL. Any idea ??
func xmlParser() {
let urlXml = "https://images.apple.com/main/rss/hotnews/hotnews.rss"
Alamofire.request(urlXml, method: .get).responseXMLObject { (response: DataResponse<RSSFeed>) in
let rssFeed = response.result.value
print(rssFeed?.channel?.items?.first?.title ?? "nil")
}
}
It seems that something is wrong with your model. I managed to map the response from this link.
Try using the following structure and compare it with yours to see the difference:
class RSSFeed: XMLMappable {
var nodeName: String!
var channel: Channel?
required init?(map: XMLMap) {}
func mapping(map: XMLMap) {
channel <- map["channel"]
}
}
class Channel: XMLMappable {
var nodeName: String!
var title: String?
var link: URL?
var description: String?
var language: String?
var copyright: String?
var pubDate: String?
var lastBuildDate: String?
var category: String?
var generator: String?
var docs: URL?
var items: [Item]?
required init?(map: XMLMap) {}
func mapping(map: XMLMap) {
title <- map["title"]
link <- (map["link"], XMLURLTransform())
description <- map["description"]
language <- map["language"]
copyright <- map["copyright"]
pubDate <- map["pubDate"]
lastBuildDate <- map["lastBuildDate"]
category <- map["category"]
generator <- map["generator"]
docs <- (map["docs"], XMLURLTransform())
items <- map["item"]
}
}
class Item: XMLMappable {
var nodeName: String!
var title: String?
var link: URL?
var description: String?
var pubDate: String?
required init?(map: XMLMap) {}
func mapping(map: XMLMap) {
title <- map["title"]
link <- (map["link"], XMLURLTransform())
description <- map["description"]
pubDate <- map["pubDate"]
}
}
Hope this helps.

Alamofire ObjectMapper - Extract common fields from all models and map them

There are few fields common in all models returned by api. But they don't come as a separate object. They are there among other fields.
Example of two models :
Event :
[
{
"event":{
"id":3,
"company_id":18,
"archived":false,
"created_by":229,
"updated_by":229,
"owner_id":229,
"subject":"",
"start_date":null,
"end_date":null,
"name":null,
"name_class_name":"",
"related_to":null,
"related_to_class_name":"",
"status":"",
"created_at":"2018-05-07T01:59:38.921-04:00",
"updated_at":"2018-05-07T01:59:38.921-04:00",
"custom_nf":false
}
}
]
Opportunity :
[
{
"opportunity":{
"id":4,
"company_id":18,
"archived":false,
"created_by":229,
"updated_by":229,
"owner_id":229,
"account_id":null,
"name":"",
"lead_source":"",
"amount":null,
"close_date":null,
"probability":null,
"stage":"",
"created_at":"2018-05-07T01:49:55.441-04:00",
"updated_at":"2018-05-07T01:49:55.441-04:00"
}
}
]
As shown, initial fields are common in both (all) models - id, company_id, archived, created_by and so on.
There are tons of projects I have worked with ObjectMapper but didn't encounter this before. I am fully aware of handling nested models, but this is a different case.
Though I can easily handle this by repeating all common fields in all models. But this doesn't sound good.
What I am looking is a way I can create a separate model class with all common fields. But the question is - how would I map that with api response using ObjectMapper ?
Just as an example, this is how I have created Opportunity model :
import UIKit
import ObjectMapper
class Opportunity: NSObject, Mappable {
var id: Int?
var companyId: Int?
var archived: Int?
var createdBy: Int?
var updatedBy: Int?
var ownerId: Int?
var accountId: Int?
var name: String?
var leadSource: String?
var amount: String?
var closeDate: String?
var probability: String?
var stage: String?
var createdAt: String?
var updatedAt: String?
required init?(map: Map) {
}
func mapping(map: Map) {
self.id <- map["id"]
self.companyId <- map["company_id"]
self.archived <- map["archived"]
self.createdBy <- map["created_by"]
self.updatedBy <- map["updated_by"]
self.ownerId <- map["owner_id"]
self.accountId <- map["account_id"]
self.name <- map["name"]
self.leadSource <- map["lead_source"]
self.amount <- map["amount"]
self.closeDate <- map["close_date"]
self.probability <- map["probability"]
self.stage <- map["stage"]
self.createdAt <- map["created_at"]
self.updatedAt <- map["updated_at"]
}
}
You can create base entity and put there common fields.
Example:
import UIKit
import ObjectMapper
class BaseEntity: NSObject, Mappable {
var id: Int?
var companyId: Int?
var archived: Int?
var createdBy: Int?
var updatedBy: Int?
var ownerId: Int?
var name: String?
var createdAt: String?
var updatedAt: String?
required init?(map: Map) {
}
func mapping(map: Map) {
self.id <- map["id"]
self.companyId <- map["company_id"]
self.archived <- map["archived"]
self.createdBy <- map["created_by"]
self.updatedBy <- map["updated_by"]
self.ownerId <- map["owner_id"]
self.name <- map["name"]
self.createdAt <- map["created_at"]
self.updatedAt <- map["updated_at"]
}
}
class Opportunity: BaseEntity {
var accountId: Int?
var leadSource: String?
var amount: String?
var closeDate: String?
var probability: String?
var stage: String?
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
self.accountId <- map["account_id"]
self.leadSource <- map["lead_source"]
self.amount <- map["amount"]
self.closeDate <- map["close_date"]
self.probability <- map["probability"]
self.stage <- map["stage"]
}
}
Note: BaseEntity isn't good name, I think you can give better name.

Json parsing Issue using objectmapper library in swift

I am facing issue with parsing response from server side. I get response in this format
1.)
For first image my model class is working fine. In this I don't null
2.)
For second image response my Model class is not working it's giving nil after parsing.In this I get null in array
My Model class for my api response is this
class GetTodayMyKpiResponse: Mappable{
var status: String?
var myKpiMonth: MyKpiMonthResponse?
var myKpiWeek: MyKpiWeekResponse?
required init?(map: Map){
}
func mapping(map: Map) {
status <- map["status"]
myKpiMonth <- map["myKpiMonth"]
myKpiWeek <- map["myKpiWeek"]
}
}
class MyKpiMonthResponse: Mappable{
var myKpiMonthYear: Double?
var myKpiMonthDetailList: [MyKpiMonthDetailResponse]?
var myKpiMonthList: [MyKpiMonthListReponse]?
required init?(map: Map) {
}
func mapping(map: Map) {
myKpiMonthYear <- map["myKpiMonthYear"]
myKpiMonthDetailList <- map["myKpiMonthDetail"]
myKpiMonthList <- map["myKpiMonthList"]
}
}
class MyKpiMonthDetailResponse: Mappable{
var myKpiMonthDetailOutletCode: String?
var myKpiMonthDetailUnitTiers: [String]?
var myKpiMonthDetailTargetUnits: [String]?
var myKpiMonthDetailBonusIncentive: Double?
var myKpiMonthDetailOutletName: String?
var myKpiMonthDetailModelName: [String]?
var myKpiMonthDetailMonth: String?
var myKpiMonthDetailType: Double?
required init?(map: Map) {
}
func mapping(map: Map) {
myKpiMonthDetailOutletCode <- map["myKpiMonthDetailOutletCode"]
myKpiMonthDetailUnitTiers <- map["myKpiMonthDetailUnitTiers"]
myKpiMonthDetailTargetUnits <- map["myKpiMonthDetailTargetUnits"]
myKpiMonthDetailBonusIncentive <- map["myKpiMonthDetailBonusIncentive"]
myKpiMonthDetailOutletName <- map["myKpiMonthDetailOutletName"]
myKpiMonthDetailModelName <- map["myKpiMonthDetailModelName"]
myKpiMonthDetailMonth <- map["myKpiMonthDetailMonth"]
myKpiMonthDetailType <- map["myKpiMonthDetailType"]
}
}
class MyKpiMonthListReponse: Mappable {
var myKpiMonthMaxUnit: Double?
var myKpiMonthDate: String?
var myKpiMonthBonusAmount: Double?
var myKpiMonthActivatedUnit: Double?
var myKpiMonthMinUnit: Double?
var myKpiMonthCurrentUnit: Double?
var myKpiMonthBonusStatus: String?
var myKpiMonthOutletName: String?
var myKpiMonthOutletAddress: String?
required init?(map: Map) {
}
func mapping(map: Map) {
myKpiMonthMaxUnit <- map["myKpiMonthMaxUnit"]
myKpiMonthDate <- map["myKpiMonthDate"]
myKpiMonthBonusAmount <- map["myKpiMonthBonusAmount"]
myKpiMonthActivatedUnit <- map["myKpiMonthActivatedUnit"]
myKpiMonthMinUnit <- map["myKpiMonthMinUnit"]
myKpiMonthCurrentUnit <- map["myKpiMonthCurrentUnit"]
myKpiMonthBonusStatus <- map["myKpiMonthBonusStatus"]
myKpiMonthOutletName <- map["myKpiMonthOutletName"]
myKpiMonthOutletAddress <- map["myKpiMonthOutletAddress"]
}
}
class MyKpiWeekResponse: Mappable{
var myKpiWeekDetail: [MyKpiWeekDetailResponse]?
var myKpiWeekList: [MyKpiWeekListResponse]?
var myKpiWeekYear: Double?
var myKpiWeekMonth: Double?
required init?(map: Map) {
}
func mapping(map: Map) {
myKpiWeekDetail <- map["myKpiWeekDetail"]
myKpiWeekList <- map["myKpiWeekList"]
myKpiWeekYear <- map["myKpiWeekYear"]
myKpiWeekMonth <- map["myKpiWeekMonth"]
}
}
class MyKpiWeekDetailResponse: Mappable{
var myKpiWeekDetailEndDate: String?
var myKpiWeekDetailUnitTiers: [String]?
var myKpiWeekDetailOutletName: String?
var myKpiWeekDetailStartDate: String?
var myKpiWeekDetailType: Double?
var myKpiWeekDetailModelName: [String]?
var myKpiWeekDetailTypeOfReward: String?
var myKpiWeekDetailOutletCode: String?
var myKpiWeekDetailTargetUnits: [String]?
required init?(map: Map) {
}
func mapping(map: Map) {
myKpiWeekDetailEndDate <- map["myKpiWeekDetailEndDate"]
myKpiWeekDetailUnitTiers <- map["myKpiWeekDetailUnitTiers"]
myKpiWeekDetailOutletName <- map["myKpiWeekDetailOutletName"]
myKpiWeekDetailStartDate <- map["myKpiWeekDetailStartDate"]
myKpiWeekDetailType <- map["myKpiWeekDetailType"]
myKpiWeekDetailModelName <- map["myKpiWeekDetailModelName"]
myKpiWeekDetailTypeOfReward <- map["myKpiWeekDetailTypeOfReward"]
myKpiWeekDetailOutletCode <- map["myKpiWeekDetailOutletCode"]
myKpiWeekDetailTargetUnits <- map["myKpiWeekDetailTargetUnits"]
}
}
class MyKpiWeekListResponse: Mappable {
var myKpiWeekBonusStatus: String?
var myKpiWeekEndDate: String?
var myKpiWeekActivatedUnit: Double?
var myKpiWeekStartDate: String?
var myKpiWeekMinUnit: Double?
var myKpiWeekCurrentUnit: Double?
var myKpiWeekOutletName: String?
var myKpiWeekTypeOfReward: String?
var myKpiWeekOutletAddress: String?
var myKpiWeekMaxUnit: Double?
required init?(map: Map) {
}
func mapping(map: Map) {
myKpiWeekBonusStatus <- map["myKpiWeekBonusStatus"]
myKpiWeekEndDate <- map["myKpiWeekEndDate"]
myKpiWeekActivatedUnit <- map["myKpiWeekActivatedUnit"]
myKpiWeekStartDate <- map["myKpiWeekStartDate"]
myKpiWeekMinUnit <- map["myKpiWeekMinUnit"]
myKpiWeekCurrentUnit <- map["myKpiWeekCurrentUnit"]
myKpiWeekOutletName <- map["myKpiWeekOutletName"]
myKpiWeekTypeOfReward <- map["myKpiWeekTypeOfReward"]
myKpiWeekOutletAddress <- map["myKpiWeekOutletAddress"]
myKpiWeekMaxUnit <- map["myKpiWeekMaxUnit"]
}
}
If there is no data available then send empty array not null. Even in myKpiMonthList doesn't contain null array. There is no logic that null value in array at index 2.
You can send empty string or empty array even. Still there is no any logic for the null value.
Either handle all null value from server side else it always crash.

Object Mapper not accessing nested Array

I have the following JSON, which I am using together with ObjectMapper:
Open Api
Response snippet
{
"data": [
{
"CategoryName": "רוגע",
"CategoryID": "63",
"CategoryDate": "2016-08-26 02:12:05",
"CategoryImage": "relax.png",
"SubCategoryArray": [
{
"SubCategoryName": "רוגע",
"SubCategoryRefID": "63",
"SubCategoryID": "86",
"SubCategoryDate": "2016-08-28 02:57:07",
"TextArray": [
{
"TextID": "32",
"Text": "<p dir=\"rtl\"><span style=\"font-size:48px\"><strong><span dir=\"RTL\" lang=\"HE\" style=\"font-family:Arial\">פרופורציה</span></strong> . הכול הבל הבלים. חולף כהרף עין. אז לנשום.</span></p>\r\n"
},
My problem is getting the data from "SubCategoryArray", and "TextArray"
I tried to do the following in my mapping:
import UIKit
import ObjectMapper
class APIResult: Mappable {
var data : [dataArray]?
required init?(map: Map){
}
func mapping(map: Map) {
data <- map["data"]
}
}
class dataArray: Mappable{
var CategoryName: String?
var CategoryID: String?
var CategoryDate: String?
var CategoryImage: String?
var SubCategoryArray: SubCategoryArray?
required init?(map: Map){
}
func mapping(map: Map) {
CategoryName <- map["CategoryName"]
CategoryID <- map["CategoryID"]
CategoryDate <- map["CategoryDate"]
CategoryImage <- map["CategoryImage"]
SubCategoryArray <- map["SubCategoryArray"]
}
}
class SubCategoryArray: Mappable {
var SubCategoryName: String?
var SubCategoryRefID: String?
var SubCategoryID: String?
var SubCategoryDate: String?
var TextArray: TextArray?
required init?(map: Map){
}
func mapping(map: Map) {
SubCategoryName <- map["SubCategoryName"]
SubCategoryRefID <- map["SubCategoryRefID"]
SubCategoryID <- map["SubCategoryID"]
SubCategoryDate <- map["SubCategoryDate"]
TextArray <- map["TextArray"]
}
}
class TextArray: Mappable {
var TextID: String?
var Text:String?
required init?(map: Map){
}
func mapping(map: Map) {
TextID <- map["TextID"]
Text <- map["Text"]
// SubCategoryID <- map["SubCategoryID"]
// SubCategoryDate <- map["SubCategoryDate"]
// TextArray <- map["TextArray"]
}
}
Please point what I am doing wrong.
This is how you would map this data
import Foundation
import ObjectMapper
class customData: Mappable {
var categoryName: String = ""
var categoryId: String = ""
var subCategoryArray: [SubCategoryArray] = []
required init?(_ map: Map) {
}
func mapping(map: Map) {
categoryName <- map["data.CategoryName"]
categoryId <- map["data.CategoryID"]
subCategoryArray <- map["data.SubCategoryArray"]
}
}
class SubCategoryArray: Mappable {
var SubCategoryName: String = ""
var SubCategoryRefID: String = ""
var textArray: [TextArray] = []
required init?(_ map: Map) {
}
func mapping(map: Map) {
SubCategoryName <- map["SubCategoryName"]
SubCategoryRefID <- map["SubCategoryRefID"]
textArray <- map["TextArray"]
}
}
class TextArray: Mappable {
var TextID: String = ""
var Text: String = ""
required init?(_ map: Map) {
}
func mapping(map: Map) {
TextID <- map["TextID"]
Text <- map["Text"]
}
}
Let me know if you find any difficulty.

How to map this JSON using ObjectMapper?

I am getting this JSON in response from a webservice. I am able to understand the structure of the file.
{"response":200,"message":"fetch successfully","data":[
{"id":1,"question":"Are you currently exercising regularly?","choices":{"too_lazy":"Too lazy","times_1_3_week":"1-3 times a week","i_love_it":"I just love it!"},"answer_select":"single"},
{"id":2,"question":"Are you active member of Gym?","choices":{"yes":"Yes","no":"No"},"answer_select":"single"}]
}
and this is what I have so far
import Foundation
import UIKit
import ObjectMapper
class YASUserQuestion: Mappable {
var question: String
var id: Int
var choices: [YASExercisingQuestionChoices]
required init?(_ map: Map) {
question = ""
id = 0
choices = []
}
func mapping(map: Map) {
question <- map["question"]
id <- map["id"]
choices <- map["choices"]
}
}
class YASExercisingQuestionChoices: Mappable {
var tooLazy: String
var times_1_3_Week: String
var ILoveIt: String
required init?(_ map: Map) {
tooLazy = ""
times_1_3_Week = ""
ILoveIt = ""
}
func mapping(map: Map) {
tooLazy <- map["too_lazy"]
times_1_3_Week <- map["times_1_3_week"]
ILoveIt <- map["i_love_it"]
}
}
class YASGymMemberQuestionChoices: Mappable {
var yes: String
var no: String
required init?(_ map: Map) {
yes = ""
no = ""
}
func mapping(map: Map) {
yes <- map["yes"]
no <- map["no"]
}
}
I am not sure how to map this JSON response so can you please guide me How can I map this JSON ???
This will be your classes structure. After making these classes, next you just have map your JSON using ObjectMapper.
import UIKit
import ObjectMapper
class UserResponse: NSObject,Mappable {
var message: String?
var response: String?
var data: [DataClass]?
override init() {
super.init()
}
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
message <- map["message"]
response <- map["response"]
data <- map["data"]
}
}
class DataClass: NSObject,Mappable {
var answer_select: String?
var ID: String?
var question: String?
var choices: [ChoicesClass]?
override init() {
super.init()
}
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
answer_select <- map["answer_select"]
ID <- map["id"]
question <- map["question"]
choices <- map["choices"]
}
}
class ChoicesClass: NSObject,Mappable {
var i_love_it: String?
var times_1_3_week: String?
var too_lazy: String?
override init() {
super.init()
}
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
i_love_it <- map["i_love_it"]
times_1_3_week <- map["times_1_3_week"]
too_lazy <- map["too_lazy"]
}
}

Resources