Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a JSON into a Model Value in Swift

In my quest to learn more about Swift, I'm looking at ways to improve my app and noticed a few places where I'm making assumptions where perhaps I shouldn't be.

When creating a new object, lets say a 'student', they need things like a name (String), age (Int) and score (Float). I read these from a JSON file, and put them into an object like this:

// note, details is a [String:Any] type
let name = details["name"] as! String
let age = details["age"] as! Int 
let score = Float(details["score"])
self.student = Student(name: name, tutor_group: tutor_group, score: score)

So my questions are as follows; 1. How should I modify my code to check that if a value is not a number, where it should be, the variable becomes just nil, or even better 0? 2. What if the key in the dictionary doesn't exist? 3. Are the different ways to do this, and if so, which is best practice?

Note that I want to keep this code as short as possible - if/else statements for each line are not what I'm looking for.

Thank you so much in advance!

like image 777
Ben Avatar asked Sep 17 '16 09:09

Ben


2 Answers

The Solution suggested by the Swift team

Recently Apple described the suggested way to face this problem.

You can define the Student struct (or class) this way

struct Student {

    let name: String
    let age: Int
    let score: Float

    init?(json: [String:Any]) {
        guard
            let name = json["name"] as? String,
            let age = json["age"] as? Int,
            let score = json["score"] as? Float
            else { return nil }
        self.name = name
        self.age = age
        self.score = score
    }
}

Benefits of this approach

  1. You encapsulate the logic to convert a JSON into a Student inside the Student struct itself
  2. If the JSON doesn't contain a valid data (e.g. there is no age field with a valid Int) then the initializer fails and returns nil. This means there is no way to create a Student with missing fields (and this is a good thing) and this scenario will not cause a crash.

More

The post I linked above also describes a more advanced approach where a missing field/value throws an exception. However if you don't need to log (or notify to the caller) why the initialization failed you don't need this.

like image 93
Luca Angeletti Avatar answered Oct 08 '22 13:10

Luca Angeletti


So my questions are as follows; 1. How should I modify my code to check that if a value is not a number, where it should be, the variable becomes just nil, or even better 0? 2. What if the key in the dictionary doesn't exist? 3. Are the different ways to do this, and if so, which is best practice?

let age = (details["age"] as? Int) ?? 0
  • In all cases, age will have the type Int
  • If the key doesn't exist, details["age"] will return nil, as? Int will return an Int? with value nil and the nil coalescing operator ?? will set the value to 0.
  • If the type isn't an Int, the conditional cast as? Int will return nil and the value will be set to 0.
  • In the expected case, age will have the Int value that was stored in details["age"].

For the other fields:

let name = (details["name"] as? String) ?? ""

// If score is stored as a String
let score = Float(details["score"] as? String ?? "") ?? 0

OR

// If score is stored as a number
let score = (details["score"] as? Float) ?? 0
like image 33
vacawama Avatar answered Oct 08 '22 12:10

vacawama