[json] Correctly Parsing JSON in Swift 3

I'm trying to fetch a JSON response and store the results in a variable. I've had versions of this code work in previous releases of Swift, until the GM version of Xcode 8 was released. I had a look at a few similar posts on StackOverflow: Swift 2 Parsing JSON - Cannot subscript a value of type 'AnyObject' and JSON Parsing in Swift 3.

However, it seems the ideas conveyed there do not apply in this scenario.

How do I correctly parse the JSON reponse in Swift 3? Has something changed in the way JSON is read in Swift 3?

Below is the code in question (it can be run in a playground):

import Cocoa

let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"

if let url = NSURL(string: url) {
    if let data = try? Data(contentsOf: url as URL) {
        do {
            let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)

        //Store response in NSDictionary for easy access
        let dict = parsedData as? NSDictionary

        let currentConditions = "\(dict!["currently"]!)"

        //This produces an error, Type 'Any' has no subscript members
        let currentTemperatureF = ("\(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue

            //Display all current conditions from API
            print(currentConditions)

            //Output the current temperature in Fahrenheit
            print(currentTemperatureF)

        }
        //else throw an error detailing what went wrong
        catch let error as NSError {
            print("Details of JSON parsing error:\n \(error)")
        }
    }
}

Edit: Here is a sample of the results from the API call after print(currentConditions)

["icon": partly-cloudy-night, "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precipIntensity": 0, "windSpeed": 6.04, "summary": Partly Cloudy, "ozone": 321.13, "temperature": 49.45, "dewPoint": 41.75, "apparentTemperature": 47, "windBearing": 332, "cloudCover": 0.28, "time": 1480846460]

This question is related to json swift parsing swift3 xcode8

The answer is


let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"

let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!

do {
    let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
    if let names = json["names"] as? [String] 
{
        print(names)
}
} catch let error as NSError {
    print("Failed to load: \(error.localizedDescription)")
}

Updated the isConnectToNetwork-Function afterwards, thanks to this post.

I wrote an extra method for it:

import SystemConfiguration

func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) {

    if(isConnectedToNetwork() == false){
        completionHandler("-1" as AnyObject)
        return
    }

    let request = NSMutableURLRequest(url: URL(string: link)!)
    request.httpMethod = "POST"
    request.httpBody = postString.data(using: String.Encoding.utf8)

    let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
        guard error == nil && data != nil else { // check for fundamental networking error
            print("error=\(error)")
            return
        }

        if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(response)")
        }
        //JSON successfull
        do {
            let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
            DispatchQueue.main.async(execute: {
                completionHandler(parseJSON as AnyObject)
            });
        } catch let error as NSError {
            print("Failed to load: \(error.localizedDescription)")
        }
    }
    task.resume()
}

func isConnectedToNetwork() -> Bool {

    var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
    zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
    zeroAddress.sin_family = sa_family_t(AF_INET)

    let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
            SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
        }
    }

    var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
    if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
        return false
    }

    let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
    let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
    let ret = (isReachable && !needsConnection)

    return ret
}

So now you can easily call this in your app wherever you want

loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") { parseJSON in

    if(String(describing: parseJSON) == "-1"){
        print("No Internet")
    } else {

    if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool {
        //... do stuff
    }
}

The problem is with the API interaction method. The JSON parsing is changed only in syntax. The main problem is with the way of fetching data. What you are using is a synchronous way of getting data. This doesn't work in every case. What you should be using is an asynchronous way to fetch data. In this way, you have to request data through the API and wait for it to respond with data. You can achieve this with URL session and third party libraries like Alamofire. Below is the code for URL Session method.

let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL.init(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
    guard error == nil else {
        print(error)
    }
    do {
        let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
        // Note if your data is coming in Array you should be using [Any]()
        //Now your data is parsed in Data variable and you can use it normally
        let currentConditions = Data["currently"] as! [String:Any]
        print(currentConditions)
        let currentTemperatureF = currentConditions["temperature"] as! Double
        print(currentTemperatureF)
    } catch let error as NSError {
        print(error)
    }
}.resume()

This is an other way to solve your problem. So please check out below solution. Hope it will help you.

let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
    let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
    if let names = json["names"] as? [String] {
        print(names)
    }
} catch let error as NSError {
    print("Failed to load: \(error.localizedDescription)")
}

Swift has a powerful type inference. Lets get rid of "if let" or "guard let" boilerplate and force unwraps using functional approach:

  1. Here is our JSON. We can use optional JSON or usual. I'm using optional in our example:
let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
  1. Helper functions. We need to write them only once and then reuse with any dictionary:
/// Curry
public func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
    return { a in
        { f(a, $0) }
    }
}

/// Function that takes key and optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key, _ json: Dictionary<Key, Any>?) -> Value? {
    return json.flatMap {
        cast($0[key])
    }
}

/// Function that takes key and return function that takes optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key) -> (Dictionary<Key, Any>?) -> Value? {
    return curry(extract)(key)
}

/// Precedence group for our operator
precedencegroup RightApplyPrecedence {
    associativity: right
    higherThan: AssignmentPrecedence
    lowerThan: TernaryPrecedence
}

/// Apply. g § f § a === g(f(a))
infix operator § : RightApplyPrecedence
public func §<A, B>(_ f: (A) -> B, _ a: A) -> B {
    return f(a)
}

/// Wrapper around operator "as".
public func cast<A, B>(_ a: A) -> B? {
    return a as? B
}
  1. And here is our magic - extract the value:
let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound

Just one line of code and no force unwraps or manual type casting. This code works in playground, so you can copy and check it. Here is an implementation on GitHub.


Swift 5 Cant fetch data from your api. Easiest way to parse json is Use Decodable protocol. Or Codable (Encodable & Decodable). For ex:

let json = """
{
    "dueDate": {
        "year": 2021,
        "month": 2,
        "day": 17
    }
}
"""

struct WrapperModel: Codable {
    var dueDate: DueDate
}

struct DueDate: Codable {
    var year: Int
    var month: Int
    var day: Int
}

let jsonData = Data(json.utf8)

let decoder = JSONDecoder()

do {
    let model = try decoder.decode(WrapperModel.self, from: jsonData)
    print(model)
} catch {
    print(error.localizedDescription)
}

A big change that happened with Xcode 8 Beta 6 for Swift 3 was that id now imports as Any rather than AnyObject.

This means that parsedData is returned as a dictionary of most likely with the type [Any:Any]. Without using a debugger I could not tell you exactly what your cast to NSDictionary will do but the error you are seeing is because dict!["currently"]! has type Any

So, how do you solve this? From the way you've referenced it, I assume dict!["currently"]! is a dictionary and so you have many options:

First you could do something like this:

let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]  

This will give you a dictionary object that you can then query for values and so you can get your temperature like this:

let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double

Or if you would prefer you can do it in line:

let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double

Hopefully this helps, I'm afraid I have not had time to write a sample app to test it.

One final note: the easiest thing to do, might be to simply cast the JSON payload into [String: AnyObject] right at the start.

let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>

I built quicktype exactly for this purpose. Just paste your sample JSON and quicktype generates this type hierarchy for your API data:

struct Forecast {
    let hourly: Hourly
    let daily: Daily
    let currently: Currently
    let flags: Flags
    let longitude: Double
    let latitude: Double
    let offset: Int
    let timezone: String
}

struct Hourly {
    let icon: String
    let data: [Currently]
    let summary: String
}

struct Daily {
    let icon: String
    let data: [Datum]
    let summary: String
}

struct Datum {
    let precipIntensityMax: Double
    let apparentTemperatureMinTime: Int
    let apparentTemperatureLowTime: Int
    let apparentTemperatureHighTime: Int
    let apparentTemperatureHigh: Double
    let apparentTemperatureLow: Double
    let apparentTemperatureMaxTime: Int
    let apparentTemperatureMax: Double
    let apparentTemperatureMin: Double
    let icon: String
    let dewPoint: Double
    let cloudCover: Double
    let humidity: Double
    let ozone: Double
    let moonPhase: Double
    let precipIntensity: Double
    let temperatureHigh: Double
    let pressure: Double
    let precipProbability: Double
    let precipIntensityMaxTime: Int
    let precipType: String?
    let sunriseTime: Int
    let summary: String
    let sunsetTime: Int
    let temperatureMax: Double
    let time: Int
    let temperatureLow: Double
    let temperatureHighTime: Int
    let temperatureLowTime: Int
    let temperatureMin: Double
    let temperatureMaxTime: Int
    let temperatureMinTime: Int
    let uvIndexTime: Int
    let windGust: Double
    let uvIndex: Int
    let windBearing: Int
    let windGustTime: Int
    let windSpeed: Double
}

struct Currently {
    let precipProbability: Double
    let humidity: Double
    let cloudCover: Double
    let apparentTemperature: Double
    let dewPoint: Double
    let ozone: Double
    let icon: String
    let precipIntensity: Double
    let temperature: Double
    let pressure: Double
    let precipType: String?
    let summary: String
    let uvIndex: Int
    let windGust: Double
    let time: Int
    let windBearing: Int
    let windSpeed: Double
}

struct Flags {
    let sources: [String]
    let isdStations: [String]
    let units: String
}

It also generates dependency-free marshaling code to coax the return value of JSONSerialization.jsonObject into a Forecast, including a convenience constructor that takes a JSON string so you can quickly parse a strongly typed Forecast value and access its fields:

let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)

You can install quicktype from npm with npm i -g quicktype or use the web UI to get the complete generated code to paste into your playground.


{
    "User":[
      {
        "FirstUser":{
        "name":"John"
        },
       "Information":"XY",
        "SecondUser":{
        "name":"Tom"
      }
     }
   ]
}

If I create model using previous json Using this link [blog]: http://www.jsoncafe.com to generate Codable structure or Any Format

Model

import Foundation
struct RootClass : Codable {
    let user : [Users]?
    enum CodingKeys: String, CodingKey {
        case user = "User"
    }

    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)
        user = try? values?.decodeIfPresent([Users].self, forKey: .user)
    }
}

struct Users : Codable {
    let firstUser : FirstUser?
    let information : String?
    let secondUser : SecondUser?
    enum CodingKeys: String, CodingKey {
        case firstUser = "FirstUser"
        case information = "Information"
        case secondUser = "SecondUser"
    }
    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)
        firstUser = try? FirstUser(from: decoder)
        information = try? values?.decodeIfPresent(String.self, forKey: .information)
        secondUser = try? SecondUser(from: decoder)
    }
}
struct SecondUser : Codable {
    let name : String?
    enum CodingKeys: String, CodingKey {
        case name = "name"
    }
    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)
        name = try? values?.decodeIfPresent(String.self, forKey: .name)
    }
}
struct FirstUser : Codable {
    let name : String?
    enum CodingKeys: String, CodingKey {
        case name = "name"
    }
    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)
        name = try? values?.decodeIfPresent(String.self, forKey: .name)
    }
}

Parse

    do {
        let res = try JSONDecoder().decode(RootClass.self, from: data)
        print(res?.user?.first?.firstUser?.name ?? "Yours optional value")
    } catch {
        print(error)
    }

Examples related to json

Use NSInteger as array index Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) HTTP POST with Json on Body - Flutter/Dart Importing json file in TypeScript json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 190) Angular 5 Service to read local .json file How to import JSON File into a TypeScript file? Use Async/Await with Axios in React.js Uncaught SyntaxError: Unexpected token u in JSON at position 0 how to remove json object key and value.?

Examples related to swift

Make a VStack fill the width of the screen in SwiftUI Xcode 10.2.1 Command PhaseScriptExecution failed with a nonzero exit code Command CompileSwift failed with a nonzero exit code in Xcode 10 Convert Json string to Json object in Swift 4 iOS Swift - Get the Current Local Time and Date Timestamp Xcode 9 Swift Language Version (SWIFT_VERSION) How do I use Safe Area Layout programmatically? How can I use String substring in Swift 4? 'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator Safe Area of Xcode 9 The use of Swift 3 @objc inference in Swift 4 mode is deprecated?

Examples related to parsing

Got a NumberFormatException while trying to parse a text file for objects Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) Python/Json:Expecting property name enclosed in double quotes Correctly Parsing JSON in Swift 3 How to get response as String using retrofit without using GSON or any other library in android UIButton action in table view cell "Expected BEGIN_OBJECT but was STRING at line 1 column 1" How to convert an XML file to nice pandas dataframe? How to extract multiple JSON objects from one file? How to sum digits of an integer in java?

Examples related to swift3

Xcode 9 Swift Language Version (SWIFT_VERSION) Convert NSDate to String in iOS Swift Waiting until the task finishes Type of expression is ambiguous without more context Swift Removing object from array in Swift 3 Generate your own Error code in swift 3 Swift 3: Display Image from URL add Shadow on UIView using swift 3 how to open an URL in Swift3 Correctly Parsing JSON in Swift 3

Examples related to xcode8

Linker Command failed with exit code 1 (use -v to see invocation), Xcode 8, Swift 3 Xcode: Could not locate device support files Xcode 8 shows error that provisioning profile doesn't include signing certificate Xcode error: Code signing is required for product type 'Application' in SDK 'iOS 10.0' Correctly Parsing JSON in Swift 3 How can I delete derived data in Xcode 8? CGRectMake, CGPointMake, CGSizeMake, CGRectZero, CGPointZero is unavailable in Swift How to create dispatch queue in Swift 3 Hide strange unwanted Xcode logs Can I delete data from the iOS DeviceSupport directory?