0

I have a JSON as

{
  "code": 200,
  "status": "success",
  "data": [
    {
      "cardTypeId": 1,
      "cardInfo": {
        "background": "#4267b2",
        "userName": "abkur_rt",
        "text": "Hello Video",
        "media": {
          "mediaUrl": "",
          "mediaType": "image",
          "mediaThumbUrl": ""
        }
      }
    },
    {
      "cardTypeId": 4,
      "cardInfo": {
        "text": "Image and text",
        "media": {
          "mediaUrl": "",
          "mediaType": "image",
          "mediaThumbUrl": ""
        }
      }
    }
  ]
}

To Parse this i used ObjectMapper(https://github.com/tristanhimmelman/ObjectMapper) My query is that in my JSON i get cardInfo depending upon the cardTypeId So I made classes referring this link ObjectMapper how to map different object based on JSON to understand how to make use of custom TransformType for classes. In the link's JSON reponse has Array but in my case if cardTypeId is 1 then there is 2 fields extra where as everything same in cardInfo. So i have made classes as below but i am not sure how will i create class inheriting TransFormType.

class LBDetailsList: Mappable {

    var lbListArray : [LBDetail]?

    required init?(map: Map) {

    }

    func mapping(map: Map) {
        lbListArray <- map ["data"]
    }
}
class LBDetail: Mappable {
    var cardTypeID : Int?
    var cardInfo: LBBaseCardInfo?

    required init?(map: Map) {

    }

    func mapping(map: Map)
    {
        cardInfo <- map["cardInfo"]
    }
}
class LBBaseCardInfo: Mappable {

    var text: String?
    var media: LBMedia?

    required init?(map: Map) {

    }

    func mapping(map: Map) {
        text <- map["text"]
        media <- map["media"]
    }
}
class CardType1: LBBaseCardInfo {

    var background, userName : String?

    required init?(map: Map) {
        super.init(map: map)
    }

    override func mapping(map: Map) {
        super.mapping(map: map)
        background <- map["background"]
        userName <- map["userName"]
    }
}
class CardType2: LBBaseCardInfo {

    required init?(map: Map) {
        super.init(map: map)
    }

    override func mapping(map: Map) {
        super.mapping(map: map)
    }
}
class LBMedia: Mappable {

    var mediaURL: String?
    var mediaType: String?
    var mediaThumbURL: String?

    required init?(map: Map) {

    }

    func mapping(map: Map) {
        mediaURL <- map["mediaUrl"]
        mediaType <- map["mediaType"]
        mediaThumbURL <- map["mediaThumbUrl"]
    }
}

Please Kindly help me out to understand this framework.

user28434'mstep
  • 6,290
  • 2
  • 20
  • 35
Uzair Dhada
  • 333
  • 2
  • 6
  • 23
  • In Swift 4+ all third-party libraries for parsing JSON into objects have become obsolete in favor of the `Codable` protocol. You are encouraged to drop `ObjectMapper` and use `Codable`. – vadian Feb 20 '19 at 09:59
  • Thanks for informing but i will have to go ahead with this for now and then change in near future for sure. – Uzair Dhada Feb 20 '19 at 10:05
  • I would change right now. With `Codable` it's much easier to decode the different types – vadian Feb 20 '19 at 10:30

2 Answers2

0

You need to create in following way.

1) BaseClass:

struct BaseClass : Mappable {
    var code : Int?
    var status : String?
    var data : [CardData]?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {

        code <- map["code"]
        status <- map["status"]
        data <- map["data"]
    }
}

2) CardData:

struct CardData : Mappable {
    var cardTypeId : Int?
    var cardInfo : CardInfo?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {

        cardTypeId <- map["cardTypeId"]
        cardInfo <- map["cardInfo"]
    }
}

3) Media:

struct Media : Mappable {
    var mediaUrl : String?
    var mediaType : String?
    var mediaThumbUrl : String?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {

        mediaUrl <- map["mediaUrl"]
        mediaType <- map["mediaType"]
        mediaThumbUrl <- map["mediaThumbUrl"]
    }
}

4) CardInfo:

struct CardInfo : Mappable {
    var background : String?
    var userName : String?
    var text : String?
    var media : Media?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {

        background <- map["background"]
        userName <- map["userName"]
        text <- map["text"]
        media <- map["media"]
    }
}

I have created following Mappable structure based on your json. If your json haven't contains any keys then it will parse json with nil value.

For your CardInfo I have set var text : String? which means, If there aren't available this key, the json will be parse using nil. So when you use this, you must required to check not nil before unwrap.

I hope this will help you.

Sagar Chauhan
  • 5,715
  • 2
  • 22
  • 56
  • As you said If your json haven't contains any keys then it will parse json with nil value. This is what i dont want. I have almost 20 types i have only shown here 2 so if i use this approach i will have to add all those keys which are different in CardInfo class where then for each cards i will have variable having nil. I appreciate to try to help but this i approach i have already used and want to learn a new approach by using TransforType which is in ObjectMapper. – Uzair Dhada Feb 20 '19 at 10:03
  • Because your JSON is not fixed, you can't assume it which one will come. You must required to add all keys. – Sagar Chauhan Feb 20 '19 at 10:11
  • Kindly check this link. This may give you idea what exactly i want. https://stackoverflow.com/questions/43269448/objectmapper-how-to-map-different-object-based-on-json – Uzair Dhada Feb 20 '19 at 10:13
  • Have you got solution using above link ? :) – Sagar Chauhan Feb 20 '19 at 10:15
  • I am unable to create a class similar to `AnimalsArrayTransformType` as his example has keys different inside the Array where as in my case i have a class having different keys depending upon the cardtype. This can be achieved using that link but i am not sure how to implement it. – Uzair Dhada Feb 20 '19 at 10:19
0

I know it doesn't answer the question literally, but this is a native solution using Decodable.

It decodes the array for key data into an enum with associated values. This handles the different types very smoothly.

In the enum cardTypeId is decoded first and in a switch cardInfo is decoded depending on the type ID into the corresponding struct.

struct Response : Decodable {
    let code : Int
    let status : String
    let data : [CardData]
}

enum CardData  {
    case user(UserData), image(ImageData), unknown
}

extension CardData: Decodable {
    private enum CodingKeys: String, CodingKey {
        case cardTypeId
        case cardInfo
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(Int.self, forKey: .cardTypeId)
        switch type {
        case 1:
            let info = try container.decode(UserData.self, forKey: .cardInfo)
            self = .user(info)
        case 4:
            let info = try container.decode(ImageData.self, forKey: .cardInfo)
            self = .image(info)
        default:
            self = .unknown
        }
    }
}

struct UserData : Decodable {
    let text, userName, background : String
    let media : Media
}

struct ImageData : Decodable {
    let text : String
    let media : Media
}

struct Media : Decodable {
    let mediaUrl, mediaType, mediaThumbUrl : String
}

let jsonString = """
{
  "code": 200,
  "status": "success",
  "data": [
    {
      "cardTypeId": 1,
      "cardInfo": {
        "background": "#4267b2",
        "userName": "abkur_rt",
        "text": "Hello Video",
        "media": {
          "mediaUrl": "",
          "mediaType": "image",
          "mediaThumbUrl": ""
        }
      }
    },
    {
      "cardTypeId": 4,
      "cardInfo": {
        "text": "Image and text",
        "media": {
          "mediaUrl": "",
          "mediaType": "image",
          "mediaThumbUrl": ""
        }
      }
    }
  ]
}
"""

let data = Data(jsonString.utf8)

do {
    let result = try JSONDecoder().decode(Response.self, from: data)
    let cardData = result.data
    for item in cardData {
        switch item {
        case .user(let userData) : print(userData)
        case .image(let imageData) : print(imageData)
        case .unknown: print("unknown")
        }
    }
} catch {
    print(error)
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thanks for the answer. This example will surely give me new opportunity to learn about `Codable` but i really want to know and learn how can i use TransforType of ObjectMapper. – Uzair Dhada Feb 20 '19 at 11:05