Swift 4 JSON Parsing Dealing with JSON Key Changes

In the previous post we discussed about parsing JSON using JSONDecoder. In this post we will deal with a very common problem of different keys.

At times it happens that we do not agree with the keys coming in JSON and we want to call them something else. We can implement CodingKeys in such situations and prevent mapping failure when we used JSONDecoder or JSONEncoder. We will start with our previous example given below.

import UIKit
struct Student:Decodable{
// We will make property name and JSON Key name differ
    let name:String?
    let roll:Int?
}
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let urlString = "http//:www.yoururlnotmine.com/test/blah/blah"
        guard let url  =  URL.init(string: urlString) else { return }
        URLSession.shared.dataTask(with: url) { (data, responce, error) in
            guard let data = data else{return}
            do{
            let student = try JSONDecoder().decode(Student.self, from: data)
                //mapped name to student.name
                //mapped roll to student.roll
                print(student.name ?? "no name")
            }
            catch{
                //error handling
            }
            
        }
        
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

In the code above we use JSONDecoder to map JSON, with key “name” and “roll” to student.name and student.roll. Now suppose we want to use some different naming convention or rename roll to student_roll then the code given above will fail and student.student_roll will have nil JSON has no key named student_roll and decoder will not be able to map it. We will resolve the situation using private enum CodingKeys, private because we do not want it to be accessed from outside and CodingKeys is fixed name we cannot change it if we are implementing CodingKey protocol. Code given below will make things more clear

import UIKit
struct Student:Decodable{// property name should be same as json key name
    let name:String?
    let student_roll:Int?
    private enum CodingKeys:String,CodingKey{
        case name
        case student_roll = "roll"
    }
}
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let urlString = "http//:www.yoururlnotmine.com/test/blah/blah"
        guard let url  =  URL.init(string: urlString) else { return }
        URLSession.shared.dataTask(with: url) { (data, responce, error) in
            guard let data = data else{return}
            do{
                let student = try JSONDecoder().decode(Student.self, from: data)
                //mapped name to student.name
                //mapped roll to student.student_roll
                print(student.name ?? "no name")
            }
            catch{
                //error handling
            }
            
        }
        
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

In the above code we have created a private enum CodingKeys  which conforms to String and CodingKey. We changed our property name to student_roll and in order for it to be mapped correctly we provided the raw value of JSON key in CodingKeys. So what we did was

  1. Change name of our property to our preference, student_roll in our case
  2. Create a private enum which conforms to String and CodingKey.
  3. Provide exhaustive list of all our property names as enum cases.
  4. For properties which are named differently then JSON keys we have provided JSON key as string raw value. case student_roll = “roll” 

So now JSONDecoder will success fully map roll key to our student_roll property.

A pat on the back !!