I noticed that I have a lot of repeated code for getting/sending JSON to my app's API, but the only thing that is different is what entity is being (de)serialized. So I came up with the following design (for brevity only the GET method):
HTTPClient.swift
func getEntityJSON<T: JSONSerializable>(
type: T.Type,
url: String,
completionHandler: @escaping (_ result: T?,
_ headers: [String: String]?,
_ statusCode: Int?,
_ error: Error?) -> Void) {
HttpClient.sharedInstance().getJSON(url, completionHandler: { (jsonData, headers, statusCode, error) in
if let error = error {
completionHandler(nil, headers, statusCode, error)
} else {
if let entityData = jsonData {
completionHandler(T.fromJSON(data: entityData), headers, statusCode, nil)
}
}
})
}
to intend to use it like this:
HTTPClient.sharedInstance().getEntity(type: Translation, url: url) { (translation, _, _, _) in
// do stuff with Translation instance
}
and here is the JSONSerializable protocol:
import Foundation
import SwiftyJSON
protocol Serializable: Codable {
static func deserialize<T: Codable>(data: Data) -> T?
func serialize() -> Data?
}
extension Serializable {
static func deserialize<T: Decodable>(data: Data) -> T? {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? decoder.decode(T.self, from: data)
}
func serialize() -> Data? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? encoder.encode(self)
}
}
protocol JSONSerializable: Serializable {
func toJSON() -> JSON?
static func fromJSON<T>(data: JSON) -> T?
}
extension JSONSerializable {
func toJSON() -> JSON? {
if let data = self.serialize() {
return try? JSON(data: data)
} else {
return nil
}
}
}
This allows me to define a struct like this:
Translation.swift
struct Translation: Hashable, Identifiable, JSONSerializable {
var id: UUID
...
static func fromJSON(data: JSON) -> Translation? {
and I will get serialize, deserialize, toJSON functions. But the compiler complains that Translation does not conform to JSONSerializable, due to lack of:
static func fromJSON<T>(data: JSON) -> T?. I thought I would be able to implement that function with a concrete type, Translation in this case.
I would like to be both able to do Translations.fromJSON(data: data) as well as T.fromJSON(data: data). How can I achieve this?