Search code examples
jsonswiftgenericsresponsedecode

decoding a json to generic array or class in swift


How do you decode json to a generic model in swift?
In java for decoding json I use GSON and in general it does not matter I use <T<E>> or ArrayList<E>.In swift Array is a struct and can't be inheritance and it has not implemented Decodable.

I'm looking for a generic elegant class to use in all my web service.

My scenario:
I have json response

{
"status": true,
"message": "",
"code": 200,
"response": [{
    "id": 43
}]
}

and a generic reponse model like this from web services:

class GeneralResponse< T : Decodable >:NSObject,Decodable{

    var status = false
    var message = ""
    var code = -1
    var response : T?

    private enum CodingKeys: String, CodingKey {
        case status
        case message
        case code
        case response
    }

    required public init(from decoder: Decoder) throws{
        let container = try decoder.container(keyedBy: CodingKeys.self)
        status = try container.decode(Bool.self, forKey: .status)
        message = try container.decode(String.self, forKey: .message)
        code = try container.decode(Int.self, forKey: .code)
        response = try container.decode(T.self, forKey: .response)
    }

}
class ItemDemoModel:Decodable {
     var id = -1
    private enum ItemDemModelCodingKeys : String, CodingKey {
        case id
    }
     required init(from decoder:Decoder) throws {
        let container = try decoder.container(keyedBy: ItemDemModelCodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
    }
}

response variable can be ItemDemoModel or an array of ItemDemoModel.
For example:
It can be GeneralResponse<Array<ItemDemoModel>>> or GeneralResponse<ItemDemoModel>>

thanks.


Solution

  • If you declare a Decodable properties with same name as the key in json then you don't really need an enum to define Coding keys and an initializer to manually map every property with the key.

    Also, there is no need to inherit from NSObject in Swift until you have a specific use case for that. Looking at the declaration, it seems unnecessary so your GeneralResponse can be redeclared as simple as this,

    class GeneralResponse<T: Decodable>: Decodable {
    
        var code: Int
        var status: Bool
        var message: String?
        var response : T?
    }
    

    Similarly, ItemDemoModel can be declared as this,

    class ItemDemoModel: Decodable {
         var id: Int
    }
    

    Now you can setup your service as below to get the GeneralResponse<T> for any request,

    struct RequestObject {
        var method: String
        var path: String
        var params: [String: Any]
    }
    
    class WebService {
    
        private let decoder: JSONDecoder
    
        public init(_ decoder: JSONDecoder = JSONDecoder()) {
            self.decoder = decoder
        }
    
        public func decoded<T: Decodable>(_ objectType: T.Type,
                                          with request: RequestObject,
                                          completion: @escaping  (GeneralResponse<T>?, Error?) -> Void)  {
            // Here you should get data from the network call. 
            // For compilation, we can create an empty object.
            let data = Data()
    
            // Now parsing
            do {
                let response  = try self.decoder.decode(GeneralResponse<T>.self, from: data)
                completion(response, nil)
            } catch {
                completion(nil, error)
            }
        }
    }
    

    Usage

    let request = RequestObject(method: "GET", path: "https://url.com", params: [:])
    WebService().decoded([ItemDemoModel].self, with: request) { (response, error) in
        if let items = response?.response {
            print(items)
        }
    }
    

    P.S; You must be used to declare arrays and dictionaries as below,

    let array: Array<SomeType>
    let dictionary: Dictionary<String: SomeType>
    let arrayOfDictionary: Array<Dictionary<String: SomeType>>
    

    But with Swift's type inference, you can declare an array and a dictionary as simple as below,

    let array: [SomeType]
    let dictionary: [String: SomeType]
    let arrayOfDictionary: [[String: SomeType]]