Swift generic Serializable protocol

koleS

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?

Sweeper

Generics (<T>) means that your method can work with any type that the caller (not the callee, not the implementer, not anyone else) specifies. In the case of fromJSON, this is clearly not the case. Any concrete implementation of fromJSON only works with T being the enclosing type, so fromJSON is not suitable to be generic.

When a method "only works with T being the enclosing type", it is the use case of Self types:

protocol JSONSerializable: Serializable {
    func toJSON() -> JSON?
    static func fromJSON(data: JSON) -> Self?
}

This way, your Translation implementation would conform to the protocol.

Similarly, deserialise should also be declared as:

static func deserialize(data: Data) -> Self?

If you actually want a JSONSerialisable that can turn JSON into any type that the caller wants, then it's less of a JSONSerialisable, and more of a JSONSerialiser, and it doesn't make much sense to inherit from Codable in that case.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Swift Generic Protocol

Swift generic type that conform to protocol cannot be used to refer protocol?

Swift - Typealias dictionary with value that implements a generic protocol

Swift Protocol Inheritance & Generic functions

How create dependent generic protocol in swift

Swift Generic UIView subclass with protocol issue

Generic constraint for any protocol in Swift

Swift typealias in protocol and generic types

Cast a Swift generic class to a protocol with a typealias

Swift Type Erasure with Generic Enum and Generic Protocol

Swift: Generic Type conform Protocol

Swift Protocol as Generic Parameter to Class

Swift: conformance to the Protocol with Generic method with "where" clause

Swift: Generic's type protocol not being recognized

Swift using generic in protocol

Swift Generic Protocol Class Type in Array

Generic Decoder for Swift using a protocol

Using Generic swift enum in a protocol

Swift protocol conformance when returning a generic

Is it possible to have a property containing generic protocol in Swift?

Implement delegate using generic protocol in Swift

Conforming a generic type to a protocol in a Swift extension

Further constraining a generic function from a Swift Protocol

How to construct generic without protocol in Swift?

Swift Generic Protocol Function Parameters

Swift: How to pass the Generic protocol as a parameter for another protocol

Swift protocol with generic method: invalid redeclaration of implementation

Generic protocol for observing changes in Swift 5

How to conform to a protocol with a generic superscript affordance in Swift?