Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift // Convert a String Json file to an enum case

Tags:

json

swift

I have the object below:

class Food {
    var cal: Int
    var displayName: String
    var imgUrl: String
    var dishType: DishType

    init(cal: Int, displayName: String, imgUrl: String, dishType: DishType) {
        self.cal = cal
        self.displayName = displayName
        self.imgUrl = imgUrl
        self.dishType = dishtype
    }
}

enum DishType {
    case starter
    case main
    case desert
}

And this is a part of my Alamofire request:

if let cal = foodJson["cal"].int,
    let displayName = foodJson["display_name"].string,
    let dishType = foodJson["type"].string,
    let imgUrl = foodJson["imgUrl"].string {
    let food = Food(cal: cal, displayName: displayName, imgUrl: imgUrl, dishType: ??)

    foods.append(food)

How can I convert the Json String "dishType" into a "DishType" type I created with the enum in order to correctly fill my instance of Food?

like image 698
jaykaydev Avatar asked Jan 13 '18 19:01

jaykaydev


1 Answers

You might want to specify an associated value for your enum:

enum DishType: String {
    case starter = "starter"
    case main    = "main"
    case desert  = "desert"
}

Or, more simply:

enum DishType: String {
    case starter
    case main
    case desert
}

Then you can do:

dishType = DishType(rawValue: string)

e.g.

if let dishTypeString = foodJson["type"].string,
    let dishType = DishType(rawValue: dishTypeString) {
        ...
}    

Personally, if doing Swift 4, I'd retire SwiftyJSON and use the native JSONDecoder and declare your types to be Codable. (Note, we still need to define the DishType to have associated values, like above.)

For example, let's imagine your response was something like:

{
    "foods": [{
            "cal": 800,
            "display_name": "Beef",
            "imgUrl": "http://example.com/wheres_the_beef.jpg",
            "dishType": "main"
        },
        {
            "cal": 2000,
            "display_name": "Chocolate cake",
            "imgUrl": "http://example.com/yummy.jpg",
            "dishType": "desert"
        }
    ]
}

You could then define your types like so:

struct Food: Codable {
    let cal: Int
    let displayName: String
    let imgUrl: String
    let dishType: DishType
}

enum DishType: String, Codable {
    case starter
    case main
    case desert
}

And then you can parse the response like so:

struct FoodsResponse: Codable {
    let foods: [Food]
}

Alamofire.request(url)
    .responseData { response in
        switch response.result {
        case .success(let data):
            do {
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                let responseObject = try decoder.decode(FoodsResponse.self, from: data)

                print(responseObject.foods)
            } catch {
                print(error)
            }

        case .failure(let error):
            print(error)
        }
}

This gets you completely out of the business of manually iterating through the results to map it to your objects.

Clearly, I assume your real response has more keys than just foods, so you'd add whatever fields you needed to FoodsResponse, but hopefully this illustrates the idea of letting JSONDecoder parse the JSON into your model structures automatically.

For more information about JSONDecoder and Codable types, see Encoding and Decoding Custom Types.


By the way, my example FoodResponse structure prompted some question why I didn't just assume the web service would return an array of Food objects. Let me explain my rationale.

A more typical structure for FoodsResponse in a web service response would be something like:

struct FoodsResponse: Codable {
    let success: Bool
    let error: String?   // only supplied if `success` was `false`
    let foods: [Food]?   // only supplied if `success` was `true`
}

In this structure, this response object can handle success scenarios, like:

{
    "success": true,
    "foods": [...]
}

Or failures:

{
    "success": false,
    "error": "No data found"
}

I think it best to have a structure that includes some common success Boolean, e.g. success, that all well-formed responses include, and then have various properties that are filled in for successes or failures, respectively.

like image 186
Rob Avatar answered Oct 15 '22 12:10

Rob