skymook
skymook

Reputation: 3676

Alamofire 5 - DataRequest with SwiftyJSON as response

Before you tell me to use Decodable for all the models, and you would be right, I am trying to fix a project in the time the client has allocated. Changing all models to Decodable, and all the places where the models get used, would take a number of days.

The project intercepts calls, turns them to SwiftyJSON and returns custom error messages from the API.

The issue is that Alamofire 5 doesn't accept a closure as a DataRequest anymore. So I need another way of intercepting the response, checking for these custom error messages, and then returning SwiftyJSON to the caller. All models have an init that creates a SwiftyJSON model.

What models look like - simplified:

final public class SomeImage: ResponseCollectionSerializable {
    
    public static func collection(json: JSON) -> [SomeImage] {
        var images = [SomeImage]()
        
        for (_, subJson):(String, JSON) in json["children"] {
            if let image = SomeImage(json: subJson) {
                images.append(image)
            }
        }
        
        return images
    }
    
    init?(json: JSON) {
        
        guard let id = json["attributes"]["id"].string, let name = json["name"].string else {
            return nil
        }
        
        imageURL = json["attributes"]["imageURL"].string
        
        super.init(withId: id, name: name)
    }
    
    // Attributes
    let imageURL: String?
}

Protocol on collection models:

public protocol ResponseCollectionSerializable {
    static func collection(json: JSON) -> [Self]
}

Problem area:

extension Alamofire.DataRequest {
    
    public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: @escaping (DataResponse<[T], AFError>) -> Void) -> Self {
        let responseSerializer = DataResponseSerializer<[T]> { request, response, data, error in
        // error: Trailing closure passed to parameter of type 'DataPreprocessor' that does not accept a closure

            guard error == nil else {
                return .failure(error!)
            }
            
            let JSONSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
            let result = JSONSerializer.serializeResponse(request, response, data, error)
            
            switch result {
            case .success(let value):
                if response != nil  {
                    let json = JSON(value)
                    return .success(T.collection(json: json))
                } else {
                    let failureReason = "Response collection could not be serialized"
                    let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
                    let error = NSError(domain: Bundle.main.bundleIdentifier!, code: MyErrorCode.JSONSerializationFailed.rawValue, userInfo: userInfo)
                    return .failure(error) // custom Error
                }
            case .failure(let error):
                return .failure(error) // AFError
        }
        
        return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
    }
}

In simple terms, my question is how in Alamofire 5 can I run this intercepting code on all calls and return SwiftyJSON?

Upvotes: 0

Views: 1005

Answers (1)

Jon Shier
Jon Shier

Reputation: 12770

If you want to create a custom response serializer you need to conform to DataResponseSerializerProtocol (or ResponseSerializer if you support downloads as well). Here's an example from our documentation:

struct CommaDelimitedSerializer: ResponseSerializer {
    func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> [String] {
        // Call the existing StringResponseSerializer to get many behaviors automatically.
        let string = try StringResponseSerializer().serialize(request: request, 
                                                              response: response, 
                                                              data: data, 
                                                              error: error)
        
        return Array(string.split(separator: ","))
    }
}

Then you would use it with response(using:) or create your own extension to DataReqeuest to create the responseCollection method you had before.

Before doing any of this I would instead switch to using Decodable and our responseDecodable method. There's no reason to write any of this code yourself just for JSON parsing.

Upvotes: 1

Related Questions