Search code examples
iosswiftobjective-ccodable

How to directly convert a Dictionary to a Codable instance in Swift?


My app receives binary data from our server. In my old Objective-C code, I handle the response data as follows:

  1. Use NSJSONSerialization to convert NSData to NSDictionary, which contains data like: {"code": "0", "info": {"user_id": 123456}},
  2. Write a MTLModel subclass
  3. Use Mantle API to convert the above info dictionary, which is {"user_id": 123456}, into my model

Now I 'd like to use Swift to do this, and I just learnt how convenience Codable protocol is. However, looks like with this protocol implemented, I can only convert it with Data/NSData, which makes the above procedures become this:

  1. Same as above
  2. Write a struct/class that conforms to Codable protocol
  3. Use NSJSONSerialization to encode info dictionary, which is {"user_id": 123456}, into Data/NSData again
  4. Use JSONDecoder to decode this data into my model

So my question is, can a Codable object be derived directly from a dictionary?

EDIT:

Thanks for the answers from you guys, but let me clarify it a bit. Although the response JSON format is somewhat fixed, there are many different requests and responses, so the info part is different. For example:

  • R1: {"code": "0", "info": {"user_id": 123456}} is the response of a user id request
  • R2: {"code": "0", "info": {"temperature": 20, "country": "London"}} is the response of a weather temperature request

Therefore, the Codable class/struct should be constructed from only the info part, instead of the whole response data, and that's why I can't simply apply step 2 & 4 to accomplish that.


Solution

  • It looks like you have JSON that looks like this:

    let r1 = Data("""
    {"code": "0", "info": {"user_id": 123456}}
    """.utf8)
    
    let r2 = Data("""
    {"code": "0", "info": {"temperature": 20, "country": "London"}}
    """.utf8)
    

    And "info" types that look like this:

    struct UserID: Codable {
        var userId: Int
    }
    
    struct WeatherTemperature: Codable {
        var temperature: Int
        var country: String
    }
    

    I'm assuming a decoder that does the snakeCase conversion (you can replace this by implementing CodingKeys or whatever):

    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    

    Given that, you need a generic Response type:

    struct Response<Info: Codable>: Codable {
        var code: String
        var info: Info
    }
    

    And with that, you can decode your response directly from the JSON object:

    let userID = try decoder.decode(Response<UserID>.self, from: r1).info
    let weather = try decoder.decode(Response<WeatherTemperature>.self, from: r2).info