borchero/Squid

Set a default, global encoder/decoder

kevinrenskers opened this issue · 6 comments

The API that I am working with does not use snake_case, but rather camelCase. That means that I now have to supply my own JSONEncoder every time I use a HttpData.Json body. Would it be possible to set a global default instead?

The same would be very useful to set a global dateEncodingStrategy.

Makes sense, I'll think about how to integrate this properly.

While I can use my own JSONEncoder on every instance of HttpData.Json (not ideal but possible), it seems impossible to use my own JSONDecoder on the response. So it's currently not possible to change dateEncodingStrategy for the decoding of JSON, which means I have to store dates as strings now, and then manually format them to Date objects.

Do you have any idea how to solve this problem elegantly? I haven't come up with a way that aligns with the design of the library as a whole, but it should definitely be part of the library ...

I think it's tricky. I'd probably just go with a singleton configuration object where you can set your own encoder and decoder. Squid.config.decoder = ... or something like that. Adding it decoder to HttpService would be nicer, but not sure how easy it is to get a hold of that when making the request?

But you'd still want to be able to override it per request as well I imagine. So maybe make it possible to set your own decoder on JsonRequest. You can already set your own encoder on HttpData.Json so that should be good to keep as-is I guess.

aplr commented

Picking up on this old topic, I thought about this too, but it does not really make sense to add a global JSON encoder/decoder to the HttpService as it should not behave differently for only json requests. So maybe it makes sense to create a JsonHttpService which, when facing JsonRequests, falls back to the globally defined decoder when decoding. However, the suggestion of a global encoder/decoder in the config like Squid.config.decoder = ... is also tempting.

For the while being, what I'm doing right now is defining a global encoder/decoder by myself, overwriting the default ones on my custom JsonRequests and HttpBodys like so:

extension MyApi {
    
    static var encoder: JSONEncoder = {
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .useDefaultKeys
        return encoder
    }()
    
    static var decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .useDefaultKeys
        decoder.dateDecodingStrategy = .custom({ decoder -> Date in
            let container = try decoder.singleValueContainer()
            let dateString = try container.decode(String.self)
                        
            guard let date = ISO8601DateFormatter.default.date(from: dateString) else {
                throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)")
            }
            
            return date
        })
        return decoder
    }()
    
}

Then, in the request, I do the following:

struct CreateUserRequest: JsonRequest {

    typealias Result = UserDto

    let user: UserData

    var routes: HttpRoute {
        ["user"]
    }
    
    var method: HttpMethod = .post
    
    var body: HttpBody {
        HttpData.Json(user, encoder: MyApi.encoder)
    }
    
    func decode(_ data: Data) throws -> Result {
        try MyApi.decoder.decode(Result.self, from: data)
    }
}

Hope this helps.