3

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.

2
  • On a side note, if you really aren't required to inherit from NSObject don't inherit from it. In Swift you don't need to subclass from NSObject for any class you define. Commented Dec 19, 2018 at 3:12
  • @nayem sir I went try to extend Array to implement Decodable. but Array is struct and can't be extend. Commented Dec 20, 2018 at 17:27

4 Answers 4

5

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]]
Sign up to request clarification or add additional context in comments.

2 Comments

@Karman thanks for tip of omitting enum when property and json fields are same. I don't know why when I xcode complained about implementing Decodables method, but now it's ok.
what should we do in case our parant JSON in form of eaither Dictionary OR Array?
3

Here you have a function you may want to use in order to decode your JSON:

func decode<T: Decodable>(_ data: Data, completion: @escaping ((T) -> Void)) {
    do {
        let model = try JSONDecoder().decode(T.self, from: data)
        completion(model)
    } catch {
        log(error.localizedDescription, level: .error)
    }
}

So you can just call your function like:

decode(data, completion: { (user: User) in
            // Do something with your parsed user struct or whatever you wanna parse
        })

I hope this helps :D

1 Comment

I would suggest removing log(_:level:) and putting something more standard in there, like a print statement and remove the localizedDescription from the error.
2

Array<T> conforms to Decodable if T conforms to Decodable, so GeneralResponse<[ItemDemoModel]> won't produce any errors.

As shown here:

enter image description here

You can simply do this:

let decoder = JSONDecoder()
let obj = try decoder.decode(type, from: json.data(using: .utf8)!)

1 Comment

sir you are right. your codes works but how this works, Array does not confirm Decodable.
0

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,

struct ResponseModel<T: Decodable>: Decodable {

var errorCode: Int?
var body: T?
var message: String?
var success: Bool?

}

If you are using different key than response

struct ResponseModel<T: Decodable>: Decodable {

var errorCode: Int?
var body: T?
var message: String?
var success: Bool?

enum CodingKeys: String, CodingKey {

    case errorCode
    case body
    case message = "response_Message"
    case success
}

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.