Search code examples
jsonswiftrestalamofire

Swift/AlamoFire: Unable to decode JSON response


I'm trying to make requests using AlamoFire and decode them using the built-in .responseDecodable handler. The API endpoint I'm hitting is https://api.linode.com/v4/regions.

Request.swift:

import Foundation
import Alamofire

func getRegions() {
        
    AF.request("https://api.linode.com/v4/regions")
        .validate()
        .responseDecodable(of: Region.self) { (response) in
            guard let regions = response.value else { return }
            print(regions)
            debugPrint("Response: \(response)")
        }
    
}

When I built and run the binary and call getRegions(), I get no output in the console (debug or otherwise). When I change getRegions() and remove .responseDecodable, like this, I get the raw JSON in the console:

    let regionRequest = AF.request("https://api.linode.com/v4/regions")
    regionRequest.responseJSON { (data) in
        print(data)

Here is my Region model:

Region.swift:

import Foundation

struct Region: Decodable {
    
    enum resolvers: String, Decodable {
        case ipv4, ipv6
    }
    
    let capabilities: Array<String>
    let country: String
    let id: String
    let resolvers: Set<resolvers>
    let status: String
    let page: Int
    let pages: Int
    let results: Int
    
    enum CodingKeys: String, CodingKey {
        
        case capabilities
        case country
        case resolvers
        case status
        case id
        case page
        case pages
        case results
        
    }
    
}

And here is the JSON response from the API:

data =     (
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                "GPU Linodes",
                Kubernetes,
                "Cloud Firewall",
                Vlans
            );
            country = in;
            id = "ap-west";
            resolvers =             {
                ipv4 = "172.105.34.5,172.105.35.5,172.105.36.5,172.105.37.5,172.105.38.5,172.105.39.5,172.105.40.5,172.105.41.5,172.105.42.5,172.105.43.5";
                ipv6 = "2400:8904::f03c:91ff:fea5:659,2400:8904::f03c:91ff:fea5:9282,2400:8904::f03c:91ff:fea5:b9b3,2400:8904::f03c:91ff:fea5:925a,2400:8904::f03c:91ff:fea5:22cb,2400:8904::f03c:91ff:fea5:227a,2400:8904::f03c:91ff:fea5:924c,2400:8904::f03c:91ff:fea5:f7e2,2400:8904::f03c:91ff:fea5:2205,2400:8904::f03c:91ff:fea5:9207";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                Kubernetes,
                "Cloud Firewall",
                Vlans
            );
            country = ca;
            id = "ca-central";
            resolvers =             {
                ipv4 = "172.105.0.5,172.105.3.5,172.105.4.5,172.105.5.5,172.105.6.5,172.105.7.5,172.105.8.5,172.105.9.5,172.105.10.5,172.105.11.5";
                ipv6 = "2600:3c04::f03c:91ff:fea9:f63,2600:3c04::f03c:91ff:fea9:f6d,2600:3c04::f03c:91ff:fea9:f80,2600:3c04::f03c:91ff:fea9:f0f,2600:3c04::f03c:91ff:fea9:f99,2600:3c04::f03c:91ff:fea9:fbd,2600:3c04::f03c:91ff:fea9:fdd,2600:3c04::f03c:91ff:fea9:fe2,2600:3c04::f03c:91ff:fea9:f68,2600:3c04::f03c:91ff:fea9:f4a";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                Kubernetes,
                "Cloud Firewall",
                Vlans
            );
            country = au;
            id = "ap-southeast";
            resolvers =             {
                ipv4 = "172.105.166.5,172.105.169.5,172.105.168.5,172.105.172.5,172.105.162.5,172.105.170.5,172.105.167.5,172.105.171.5,172.105.181.5,172.105.161.5";
                ipv6 = "2400:8907::f03c:92ff:fe6e:ec8,2400:8907::f03c:92ff:fe6e:98e4,2400:8907::f03c:92ff:fe6e:1c58,2400:8907::f03c:92ff:fe6e:c299,2400:8907::f03c:92ff:fe6e:c210,2400:8907::f03c:92ff:fe6e:c219,2400:8907::f03c:92ff:fe6e:1c5c,2400:8907::f03c:92ff:fe6e:c24e,2400:8907::f03c:92ff:fe6e:e6b,2400:8907::f03c:92ff:fe6e:e3d";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                Kubernetes,
                "Cloud Firewall"
            );
            country = us;
            id = "us-central";
            resolvers =             {
                ipv4 = "72.14.179.5,72.14.188.5,173.255.199.5,66.228.53.5,96.126.122.5,96.126.124.5,96.126.127.5,198.58.107.5,198.58.111.5,23.239.24.5";
                ipv6 = "2600:3c00::2,2600:3c00::9,2600:3c00::7,2600:3c00::5,2600:3c00::3,2600:3c00::8,2600:3c00::6,2600:3c00::4,2600:3c00::c,2600:3c00::b";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                Kubernetes,
                "Cloud Firewall"
            );
            country = us;
            id = "us-west";
            resolvers =             {
                ipv4 = "173.230.145.5,173.230.147.5,173.230.155.5,173.255.212.5,173.255.219.5,173.255.241.5,173.255.243.5,173.255.244.5,74.207.241.5,74.207.242.5";
                ipv6 = "2600:3c01::2,2600:3c01::9,2600:3c01::5,2600:3c01::7,2600:3c01::3,2600:3c01::8,2600:3c01::4,2600:3c01::b,2600:3c01::c,2600:3c01::6";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Cloud Firewall",
                Vlans
            );
            country = us;
            id = "us-southeast";
            resolvers =             {
                ipv4 = "74.207.231.5,173.230.128.5,173.230.129.5,173.230.136.5,173.230.140.5,66.228.59.5,66.228.62.5,50.116.35.5,50.116.41.5,23.239.18.5";
                ipv6 = "2600:3c02::3,2600:3c02::5,2600:3c02::4,2600:3c02::6,2600:3c02::c,2600:3c02::7,2600:3c02::2,2600:3c02::9,2600:3c02::8,2600:3c02::b";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                "Object Storage",
                "GPU Linodes",
                Kubernetes
            );
            country = us;
            id = "us-east";
            resolvers =             {
                ipv4 = "66.228.42.5,96.126.106.5,50.116.53.5,50.116.58.5,50.116.61.5,50.116.62.5,66.175.211.5,97.107.133.4,207.192.69.4,207.192.69.5";
                ipv6 = "2600:3c03::7,2600:3c03::4,2600:3c03::9,2600:3c03::6,2600:3c03::3,2600:3c03::c,2600:3c03::5,2600:3c03::b,2600:3c03::2,2600:3c03::8";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                Kubernetes,
                "Cloud Firewall"
            );
            country = uk;
            id = "eu-west";
            resolvers =             {
                ipv4 = "178.79.182.5,176.58.107.5,176.58.116.5,176.58.121.5,151.236.220.5,212.71.252.5,212.71.253.5,109.74.192.20,109.74.193.20,109.74.194.20";
                ipv6 = "2a01:7e00::9,2a01:7e00::3,2a01:7e00::c,2a01:7e00::5,2a01:7e00::6,2a01:7e00::8,2a01:7e00::b,2a01:7e00::4,2a01:7e00::7,2a01:7e00::2";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                "Object Storage",
                "GPU Linodes",
                Kubernetes
            );
            country = sg;
            id = "ap-south";
            resolvers =             {
                ipv4 = "139.162.11.5,139.162.13.5,139.162.14.5,139.162.15.5,139.162.16.5,139.162.21.5,139.162.27.5,103.3.60.18,103.3.60.19,103.3.60.20";
                ipv6 = "2400:8901::5,2400:8901::4,2400:8901::b,2400:8901::3,2400:8901::9,2400:8901::2,2400:8901::8,2400:8901::7,2400:8901::c,2400:8901::6";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                "Object Storage",
                "GPU Linodes",
                Kubernetes
            );
            country = de;
            id = "eu-central";
            resolvers =             {
                ipv4 = "139.162.130.5,139.162.131.5,139.162.132.5,139.162.133.5,139.162.134.5,139.162.135.5,139.162.136.5,139.162.137.5,139.162.138.5,139.162.139.5";
                ipv6 = "2a01:7e01::5,2a01:7e01::9,2a01:7e01::7,2a01:7e01::c,2a01:7e01::2,2a01:7e01::4,2a01:7e01::3,2a01:7e01::6,2a01:7e01::b,2a01:7e01::8";
            };
            status = ok;
        },
                {
            capabilities =             (
                Linodes,
                NodeBalancers,
                "Block Storage",
                Kubernetes
            );
            country = jp;
            id = "ap-northeast";
            resolvers =             {
                ipv4 = "139.162.66.5,139.162.67.5,139.162.68.5,139.162.69.5,139.162.70.5,139.162.71.5,139.162.72.5,139.162.73.5,139.162.74.5,139.162.75.5";
                ipv6 = "2400:8902::3,2400:8902::6,2400:8902::c,2400:8902::4,2400:8902::2,2400:8902::8,2400:8902::7,2400:8902::5,2400:8902::b,2400:8902::9";
            };
            status = ok;
        }
    );
    page = 1;
    pages = 1;
    results = 11;
})

What am I doing wrong here? I suspect it's something with the Region struct but I haven't been able to figure it out. Any help would be appreciated, thanks!

Update:

I simplified my Region model a bit and also added optionals:

struct Data: Codable {
    
    let data: [Region]
    let page, pages, results: Int
    
}
struct Region: Codable {
    
    let capabilities: [String]?
    let country, id: String?
    let status: String?
    let resolvers: Resolvers?
    
    enum CodingKeys: String, CodingKey {
        case country
        case id
        case status
        case capabilities
        case resolvers
    }
}

struct Resolvers: Codable {
    let ipv4, ipv6: String
}

I'm now getting a successful response using AlamoFire (though the everything is nil – progress though!):

success(.Region(capabilities: nil, country: nil, id: nil, status: nil, resolvers: nil))

Solution

  • Thanks to everyone who commented! I was able to sort this out by adding a missing coding key. I created Regions.swift, which looks like this:

    import Foundation
    
    struct Regions: Decodable {
        
        let all: [Region]
        
        enum CodingKeys: String, CodingKey {
            case all = "data"
        }
    }
    

    The API response is encapsulated in data. Once I added this coding key, I was able to get the decoded response I was looking for:

       AF.request("https://api.linode.com/v4/regions")
             .validate()
            .responseDecodable(of: Regions.self) {(response) in
                guard let regions = response.value else {return}
                print(regions.all[0].self)
            }
    

    Response:

    Region(capabilities: ["Linodes", "NodeBalancers", "Block Storage", "GPU Linodes", "Kubernetes", "Cloud Firewall", "Vlans"], country: "in", id: "ap-west", status: "ok", resolvers: QuickCloud.Resolvers(ipv4: "172.105.34.5,172.105.35.5,172.105.36.5,172.105.37.5,172.105.38.5,172.105.39.5,172.105.40.5,172.105.41.5,172.105.42.5,172.105.43.5", ipv6: "2400:8904::f03c:91ff:fea5:659,2400:8904::f03c:91ff:fea5:9282,2400:8904::f03c:91ff:fea5:b9b3,2400:8904::f03c:91ff:fea5:925a,2400:8904::f03c:91ff:fea5:22cb,2400:8904::f03c:91ff:fea5:227a,2400:8904::f03c:91ff:fea5:924c,2400:8904::f03c:91ff:fea5:f7e2,2400:8904::f03c:91ff:fea5:2205,2400:8904::f03c:91ff:fea5:9207"))