2019-10-09
Extending a class model for generic type Swift
stackoverflow
Question

I am passing API response with Moya and getting this value. I am able to get the object but I extented a base response to handle extra parameters but the extended value does not seem to work. The data expected could be an array of objects and it could just be a regular object. After passing this values, It stopped working and data is not got but every other parameter like status , message are passed except data. Here is my Base response and how I used it

class MaxResponseBase: Codable {
    var status: String?
    var message: String?
    var pagination: Pagination?

    var isSucessful: Bool {
        return status == "success"
    }

    struct ErrorMessage {
        static let passwordInvalid = " Current password is invalid."
        static let loginErrorIncorrectInfo = " Incorrect username/password."
        static let loginErrorAccountNotExist = " Invalid request"
    }
}

class MaxResponse<T: Codable>: MaxResponseBase {
    var data: T?
}

class MaxArrayResponse<T: Codable>: MaxResponseBase {
    var data = [T]()
}

Here is my API call for signin for example

func signin(email: String, password: String) -> Observable<MaxResponse<AuthResponse>> {
        return provider.rx.request(.signin(username: email, password: password))
            .filterSuccess()
            .mapObject(MaxResponse<AuthResponse>.self)
            .asObservable()
    }

how can I tweak this to get data object also

JSON

{
  "status" : "success",
  "data" : {
    "is_locked" : false,
    "__v" : 0,
    "created_at" : "2019-04-15T11:57:12.551Z"
  }
}

It could also be an array of data

Answer
1

(Note: all the code below can be put in a Playground to show that it works.)

In order to solve this, you have to manually write all your initializers. I posted the code that does most of it below but I strongly recommend you use structs instead of classes. It is better in every way if you use structs and containment instead of classes and inheritance.

struct Pagination: Codable { }

struct AuthResponse: Codable {
    let isLocked: Bool
    let __v: Int
    let createdAt: Date
}

class MaxResponseBase: Codable {
    let status: String?
    let message: String?
    let pagination: Pagination?

    var isSucessful: Bool {
        return status == "success"
    }

    struct ErrorMessage {
        static let passwordInvalid = " Current password is invalid."
        static let loginErrorIncorrectInfo = " Incorrect username/password."
        static let loginErrorAccountNotExist = " Invalid request"
    }

    enum CodingKeys: String, CodingKey {
        case status, message, pagination
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        status = try container.decode(String?.self, forKey: .status)
        message = try? container.decode(String?.self, forKey: .message) ?? nil
        pagination = try? container.decode(Pagination?.self, forKey: .pagination) ?? nil
    }
}

class MaxResponse<T: Codable>: MaxResponseBase {
    let data: T?

    enum DataCodingKeys: String, CodingKey {
        case data
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: DataCodingKeys.self)
        data = try container.decode(T?.self, forKey: .data)
        try super.init(from: decoder)
    }
}

let json = """
{
"status" : "success",
"data" : {
"is_locked" : false,
"__v" : 0,
"created_at" : "2019-04-15T11:57:12.551Z"
}
}
"""

let data = json.data(using: .utf8)!

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss.SSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let response = try decoder.decode(MaxResponse<AuthResponse>.self, from: data)

print(response)

It is far simpler and less code to just use a struct:

struct AuthResponse: Codable {
    struct ResponseData: Codable {
        let isLocked: Bool
        let __v: Int
        let createdAt: Date
    }
    let status: String?
    let data: ResponseData
}

let json = """
{
"status" : "success",
"data" : {
"is_locked" : false,
"__v" : 0,
"created_at" : "2019-04-15T11:57:12.551Z"
}
}
"""

let data = json.data(using: .utf8)!

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss.SSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let response = try decoder.decode(AuthResponse.self, from: data)

print(response)

And if you really need the MaxResponse type, then make it a protocol and have your other types conform to it. I'm almost willing to bet that you don't need it though.


In response to your comments, here is a generic solution using structs:

struct LoginResponseData: Codable {
    let isLocked: Bool
    let __v: Int
    let createdAt: Date
}

struct BlogResponseData: Codable {
    let xxx: Bool
    let yyy: Int
    let createdAt: Date
}

struct BaseRresponse<ResponseData: Codable>: Codable {
    let status: String?
    let data: ResponseData
}
Extending a class model for generic type Swift
See more ...