Parsing JSON data using Codable in Swift

Viewed 163

I have been using codable in Swift from a long time and it has almost solved all the issues I had until I encountered the below mentioned issue. Suppose, the data coming from the API is something like this:

 { 
     "name": "ABC",
     "contactNo": "123", 
     "gender": "M", 
     "dob": "02-01-1998", 
     "qualitifcation": "XYZ"
   }

Now, I want to parse some part of this data in One model and some part in another model and have the reference of another model in the first model. For Eg:

  struct ModelOne: Codable {
    let name: String?
    let contactNo: String?
    let modelTwoRef: ModelTwo?
    }
    
    struct ModelTwo: Codable {
    let gender: String?
    let dob: String?
    let qualification: String?
    }

I am not able to solve this problem. It would be really helpful is someone can help with the same.

3 Answers

You can solve this by overriding init(from:):

struct ModelOne: Codable {
    let name: String?
    let contactNo: String?
    let modelTwoRef: ModelTwo?

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.contactNo = try container.decode(String.self, forKey: .contactNo)
        self.modelTwoRef = try ModelTwo(from: decoder)
    }
}

struct ModelTwo: Codable {
    let gender: String?
    let dob: String?
    let qualification: String?
}

Test sample:

let jsonStr = """
    {
    "name": "ABC",
    "contactNo": "123",
    "gender": "M",
    "dob": "02-01-1998",
    "qualification": "XYZ"
    }
    """

let jsonData = Data(jsonStr.utf8)

do {
    let model = try JSONDecoder().decode(ModelOne.self, from: jsonData)
    print(model)
} catch {
    print("Error: \(error)")
}

Small diff from your question which I guess was a typo: "qualification“ vs "qualitifcation".

Create JSON decode function like below function

func decode<T: Decodable>(from data: Data, to decodableType: T.Type) -> T? {
    do {
        let decodedData = try decoder.decode(decodableType, from: data)
        return decodedData
    } catch let error {
        print(error)
    }
    return nil
}

check API response data nil or not using guard statement

guard let decoadedData = self.jsonHelper.decode(from: data, to: ModelOne.self) else {
                return
            }

You need to implement decoding by hand in ModelOne and pass on the decoder. You can do this by implementing the init method of ModelOne like this:

struct ModelOne: Codable {
    let name: String?
    let contactNo: String?
    let modelTwoRef: ModelTwo?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decodeIfPresent(String.self, forKey: .name)
        contactNo = try container.decodeIfPresent(String.self, forKey: .contactNo)
        modelTwoRef = try ModelTwo(from: decoder)
    }
    
    enum CodingKeys: String, CodingKey {
        case name, contactNo
    }
}

struct ModelTwo: Codable {
    let gender: String?
    let dob: String?
    let qualification: String?
}

I hope it solves your problem. /Martin

Related