Search code examples
jsonswiftapirestalamofire

Alamofire parse response incorrectly


Trying out the request on Postman, the "data" of the response is an empty dictionary.

postman response

However, when I try that in swift using Alamofire, "data" gets misinterpreted as an empty array. What could I be doing wrong? alamofire response

Raw response using debugPrint(response) prints the following:

[Response]:
    [Status Code]: 200
    [Headers]:
        Access-Control-Allow-Origin: *
        Alt-Svc: h3=":443"; ma=2592000, h3-29=":443"; ma=2592000, h3-Q050=":443"; ma=2592000, h3-Q046=":443"; ma=2592000, h3-Q043=":443"; ma=2592000, quic=":443"; ma=2592000; v="43,46"
        Cache-Control: no-cache, private
        Content-Encoding: br
        Content-Length: 71
        Content-Type: application/json
        Date: Tue, 31 Jan 2023 16:32:18 GMT
        Vary: Accept-Encoding
        x-powered-by: PHP/8.0.24
        x-ratelimit-limit: 60
        x-ratelimit-remaining: 59
    [Body]:
        {"status":false,"message":"Kullan\u0131c\u0131 bilgileri hatal\u0131.","data":[]}

Tried changing the encoding and headers of the request, none was helpful.


Solution

  • I've come across this odd way of representing null from a server (openweathermap), where instead of null, it uses {}. To work with this, I used a custom decoder, something like:

    // for the case where we have:  "data": { } instead of, "data": null
    
    public init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if let theData = try? values.decode([String].self, forKey: .data) {
            self.data = theData
        } else {
            self.data = nil
        }
        // ....other keys
    }
    

    with declaration, let data: [String]? or whatever the array holds.

    Note this is a JSONDecoder behaviour, not Alamofire, I was using URLSession.shared... with this. When the JSONDecoder tries to decode this {}, it crashes, because it is not an a null and it not an array either. Postman is probably more forgiving than Swift JSONDecoder. Alamofire tries to decode data as an array, because you probably declared it as an array in your struct/class to decode.

    EDIT-1:

    here is the SwiftUI code I used to test my answer (same approach for UIKit and Alamofire):

    struct RequestResponse: Decodable {
        var status: Bool
        var message: String?
        var data: [String]?
        
        enum CodingKeys: String, CodingKey {
            case status, message, data
        }
        
        // for the case where we have: "data": { } instead of, "data": null
        public init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            self.status = try values.decode(Bool.self, forKey: .status)
            self.message = try values.decode(String?.self, forKey: .message)
            if let theData = try? values.decode([String].self, forKey: .data) {
                self.data = theData
            } else {
                self.data = nil
            }
        }
    }
    
    struct ContentView: View {
        @State var response: RequestResponse?
        
        var body: some View {
            VStack {
                if let results = response {
                    Text(results.status ? "true" : "false")
                    Text(results.message ?? "no message")
                    ForEach(results.data ?? [], id: \.self) { item in
                        Text("\(item)")
                    }
                }
            }
            .onAppear {
                let json = """
    {
       "status": false,
       "message": "some message here",
       "data": {}
    }
    """
                // simulated API data from the server
                let data = json.data(using: .utf8)!
                do {
                    let decoded = try JSONDecoder().decode(RequestResponse.self, from: data)
                    print("\n---> decoded: \(decoded) \n")
                    response = decoded
                } catch {
                    print("\n---> error: \n \(error)\n")
                }
            }
        }
    }