Joshua Noble
Joshua Noble

Reputation: 894

How to guarantee valid JSON in Swift 4?

I'm trying to work with JSON data returned from a service. The JSON is, according to JSON validators, valid and is very simple:

[{"ID":"SDS-T589863","TotalRisk":0.2458,"TotalScore":641.032}] 

However trying to parse it in my Swift 4 code it is mysteriously (to me at least) invalid. Here's my attempt to parse it:

 // make the request
    let task = session.dataTask(with: urlRequest) {
        (data, response, error) in
        // check for any errors
        guard error == nil else {
            print(error!)
            return
        }
        // make sure we got data
        guard let responseData = data else {
            print("Error: did not receive data")
            return
        }

        // this is fine:
        guard let ddd = String(bytes: responseData, encoding: String.Encoding.utf8) else {
            print("can't")
            return
        }

        print(ddd) // prints [{"ID":"SDS-T589863","TotalRisk":0.2458,"TotalScore":641.032}] happily

        do {
            // cannot serialize
            guard let risk = try JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments])
                as? [String: Any]
            else {
                    print("error trying to convert data to JSON")
                    return
            }
            print(risk)
        } catch  {
            print("error trying to convert data to JSON")
            return
            }
        }
    task.resume()

}

Assuming that I have no control over the JSON object or the format in which it is returned to me, is there a way to tell what is wrong with the JSON and perhaps format the response so that it can be serialized correctly?

Upvotes: 1

Views: 844

Answers (2)

Suhit Patil
Suhit Patil

Reputation: 12023

The response type is array of json objects so you have to cast it to [[String: Any]]. Since you are using Swift 4, you can use Decodable type which maps the model to the response.

     let task = URLSession().dataTask(with: urlRequest) { (data, response, error) in
        // check for any errors
        guard error == nil else {
            print(error!)
            return
        }
        // make sure we got data
        guard let responseData = data else {
            print("Error: did not receive data")
            return
        }

        do {
            let decoder = JSONDecoder()
            let riskArray = try decoder.decode([Risk].self, from: responseData)
            print(riskArray)
        } catch {
            print("error trying to convert data to Model")
            print(error.localizedDescription)
        }
    }
    task.resume()

You can define your Model struct like

struct Risk: Decodable {
    let id: String
    let totalRisk: Double
    let totalScore: Double

    enum CodingKeys: String, CodingKey {
        case id = "ID"
        case totalRisk = "TotalRisk"
        case totalScore = "TotalScore"
    }
}

You can read more about Decodable protocol here

Upvotes: 1

Ivan Smetanin
Ivan Smetanin

Reputation: 2039

You should cast your data to the [[String: Any]] type because you have array in response.

You are trying to cast to [String: Any], but you have an array of [String: Any] because your response enclosed in [] brackets.

Example:

let risk = try JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments]) as? [[String: Any]]

Or if you want to get just only one [String: Any] object from response you can write:

let risk = (try JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments]) as? [[String: Any]])?.first

Or if your object can be an array or not an array (but it sounds a little bit strange) you could try to cast to several possible types.

Upvotes: 4

Related Questions