How to parse a multi-level json structure in Swift?

Viewed 182

The JSON from server looks like this:

A dictionary where the value is another dictionary.

{
    "S1": {
        "vpn_status": 2,
        "vpn_name": "vpn1"
    },
    "S2": {
        "vpn_status": 1,
        "vpn_name": "vpn2"
    }
}

I have created the following struct to parse it.

public struct ServerStatusResult {

    public let vpnName: String
    public let status: Int

    init?(json: [String: Any]) {
        guard
            let vpnName = json["vpn_name"] as? String,
            let status = json["vpn_status"] as? Int
            else {
                return nil
        }
        self.vpnName = vpnName
        self.status = status
    }
}

And the function to call the server is:

typealias serverStatusCompletedClosure = (_ status: Bool, _ result: Dictionary<String,ServerStatusResult>?, _ error: ServiceError?)->Void

func serverStatus(email: String, password: String, complete: @escaping serverStatusCompletedClosure) {
        let url = URL(string: "...")!
        try? self.httpClient.get(url: url,
                                token: "...",
                                email: email,
                                password: password)
        { (data, response, error) in
            if let error = error {
                complete(false, nil, ServiceError.invalidSession)
            } else if let httpResponse = response as? HTTPURLResponse {
                switch (httpResponse.statusCode) {
                case 200:
                    var result: [String:ServerStatusResult]? = nil
                    result = try! JSONSerialization.jsonObject(with: data!, options: []) as! Dictionary<String, ServerStatusResult>
                    complete(true, result, nil)

This is where my json transformation fails.

Could not cast value of type '__NSDictionaryI' (0x7fff8eaee9b0) to 'app.ServerStatusResult' (0x10021dec0).

What am I missing please?

2 Answers

You can solve it by using Decodable and using a dictionary

First make your struct conform to Decodable

public struct ServerStatusResult: Decodable {
    public let vpnName: String
    public let status: Int

    enum CodingKeys: String, CodingKey {
        case vpnName = "vpn_name"
        case status = "vpn_status"
    }
}

and then the decoding is easy

do {
    let result = try JSONDecoder().decode([String: ServerStatusResult].self, from: data)
    print(result) //or in you case complete(true, result, nil)
} catch {
    print(error)
}

You get an array of dictionary [[String: Any]]

Create a struct for a dictionary and if a dictionary has another dictionary inside it then create another struct for the inner Dictionary and create an object in the outer struct for innner dictionary json.

You can use codeable to parse your json easily, by inheriting your struct with codeable

Related