2016-09-10 23 views
90

Tôi đang cố tìm nạp phản hồi JSON và lưu trữ kết quả trong một biến. Tôi đã có phiên bản của mã này làm việc trong các phiên bản trước của Swift, cho đến khi phiên bản GM của Xcode 8 được phát hành. Tôi đã xem một số bài đăng tương tự trên StackOverflow: Swift 2 Parsing JSON - Cannot subscript a value of type 'AnyObject'JSON Parsing in Swift 3.Phân tích cú pháp chính xác JSON trong Swift 3

Tuy nhiên, có vẻ như các ý tưởng được truyền đạt không áp dụng trong trường hợp này.

Làm cách nào để phân tích cú pháp phản hồi JSON đúng cách trong Swift 3? Có điều gì đó thay đổi theo cách JSON được đọc trong Swift 3 không?

Dưới đây là mã trong câu hỏi (nó có thể được chạy trong một sân chơi):

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: Dưới đây là một mẫu của các kết quả từ các cuộc gọi API sau 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] 
+0

Bạn có thể đưa dữ liệu mẫu được trả về từ cuộc gọi API của mình không? – User

+1

Vâng, tôi vừa thêm một mẫu các kết quả được in sau khi in (currentConditions). Hy vọng nó giúp. – user2563039

Trả lời

139

Trước hết không bao giờ tải dữ liệu đồng bộ từ một URL từ xa, luôn sử dụng các phương thức không đồng bộ như URLSession.

'Any' không có thành viên subscript

xảy ra bởi vì trình biên dịch không có ý tưởng về những gì gõ các đối tượng trung gian được (ví dụ currently trong ["currently"]!["temperature"]) và kể từ khi bạn đang sử dụng loại bộ sưu tập Foundation như NSDictionary trình biên dịch không có ý tưởng nào về loại.

Ngoài ra trong Swift 3, bắt buộc phải thông báo cho trình biên dịch về loại tất cả các đối tượng được chỉ định là.

Bạn phải truyền kết quả của việc tuần tự hóa JSON vào loại thực tế.

Mã này sử dụng URLSessionđộc quyền loại bản địa Swift

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

let url = URL(string: urlString) 
URLSession.shared.dataTask(with:url!) { (data, response, error) in 
    if error != nil { 
    print(error) 
    } else { 
    do { 

     let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any] 
     let currentConditions = parsedData["currently"] as! [String:Any] 

     print(currentConditions) 

     let currentTemperatureF = currentConditions["temperature"] as! Double 
     print(currentTemperatureF) 
    } catch let error as NSError { 
     print(error) 
    } 
    } 

}.resume() 

Để in tất cả các cặp khóa/giá trị của currentConditions bạn có thể viết

let currentConditions = parsedData["currently"] as! [String:Any] 

    for (key, value) in currentConditions { 
    print("\(key) - \(value) ") 
    } 

Một lưu ý về jsonObject(with data:

Nhiều hướng dẫn (có vẻ như tất cả) gợi ý các tùy chọn .mutableContainers hoặc .mutableLeaves hoàn toàn vô nghĩa trong Swift. Hai tùy chọn là các tùy chọn Objective-C kế thừa để gán kết quả cho các đối tượng NSMutable.... Trong Swift bất kỳ var iable có thể thay đổi theo mặc định và chuyển bất kỳ tùy chọn nào trong số đó và gán kết quả cho hằng số let không có tác dụng gì cả. Hơn nữa hầu hết các triển khai không bao giờ đột biến JSON deserialized anyway.

duy nhất (hiếm) tùy chọn đó là hữu ích trong Swift là .allowFragments đó là cần thiết nếu nếu đối tượng gốc JSON có thể là một kiểu giá trị (String, Number, Bool hoặc null) chứ không phải là một trong những loại bộ sưu tập (array hoặc dictionary). Nhưng thường bỏ qua thông số options có nghĩa là Không có tùy chọn.

============================================== =============================

một số cân nhắc chung để phân tích JSON

JSON là một văn bản cũng sắp xếp định dạng. Rất dễ đọc chuỗi JSON. Đọc kỹ chuỗi. Chỉ có sáu loại khác nhau - hai loại bộ sưu tập và bốn loại giá trị.


Các loại bộ sưu tập là

  • Mảng - JSON: các đối tượng trong dấu ngoặc vuông [] - Swift: [Any] nhưng trong hầu hết trường hợp [[String:Any]]
  • điển - JSON: các đối tượng trong dấu ngoặc nhọn {} - Swift: [String:Any]

Các kiểu giá trị là

  • Chuỗi - JSON: bất kỳ giá trị trong dấu ngoặc kép "Foo", thậm chí "123" hoặc "false" - Swift: String
  • Số - JSON: giá trị số không trong đôi báo giá 123 hoặc 123.0 - Swift: Int hoặc Double
  • Bool - JSON: true hoặc falsekhông trong dấu ngoặc kép - Swift: true hoặc false
  • rỗng - JSON: null - Swift: NSNull

Theo đặc tả JSON tất cả các phím trong các từ điển được yêu cầu là String.


Về cơ bản nó luôn luôn recommeded sử dụng các ràng buộc bắt buộc để unwrap optionals an toàn

Nếu đối tượng gốc là một từ điển ({}) đúc loại để [String:Any]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ... 

và lấy giá trị bằng các phím có (OneOfSupportedJSONTypes là tập hợp hoặc loại giá trị JSON như được mô tả ở trên.)

if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes { 
    print(foo) 
} 

Nếu đối tượng gốc là một mảng ([]) đúc loại để [[String:Any]]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ... 

và lặp qua mảng với

for item in parsedData { 
    print(item) 
} 

Nếu bạn cần một mục tại cụ thể kiểm tra chỉ mục nếu chỉ mục tồn tại

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2, 
    let item = parsedData[2] as? OneOfSupportedJSONTypes { 
     print(item) 
    } 
} 

Trong trường hợp hiếm hoi mà các JSON chỉ đơn giản là một trong những loại có giá trị - chứ không phải là một loại bộ sưu tập - bạn phải vượt qua các tùy chọn .allowFragments và đúc kết quả với các loại giá trị thích hợp ví dụ

if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ... 

Apple đã công bố một bài viết toàn diện trong Blog Swift: Working with JSON in Swift

+0

Điều này rất hữu ích. Tôi chỉ tò mò tại sao mã trên không hiển thị đầu ra có thể nhìn thấy khi nó chạy trong một sân chơi. – user2563039

+0

Như đã đề cập ở trên, bạn cần truyền kết quả mơ hồ của 'dict! [" Hiện tại "]! Vào từ điển mà trình biên dịch có thể suy ra một cách an toàn đăng ký khóa tiếp theo. – vadian

+1

Bài viết của Apple khá tuyệt, nhưng tôi không thể chạy nó nhanh bằng 3 vì lý do nào đó. Nó phàn nàn về Type [String: Any]? không có thành viên subscript nào. Có một số vấn đề khác với nó nữa, nhưng tôi đã có thể vượt qua nó. Bất cứ ai cũng có một ví dụ về mã của họ thực sự chạy? – Shades

12

một sự thay đổi lớn đã xảy ra với Xcode 8 Beta 6 cho Swift 3 là id đó hiện đang nhập dưới dạng Any thay vì AnyObject.

Điều này có nghĩa là parsedData được trả lại dưới dạng từ điển có nhiều khả năng nhất với loại [Any:Any]. Nếu không sử dụng trình gỡ lỗi, tôi không thể cho bạn biết chính xác nội dung truyền của bạn tới NSDictionary sẽ làm nhưng lỗi bạn thấy là vì dict!["currently"]! có loại Any

Vì vậy, bạn giải quyết vấn đề này như thế nào? Từ cách bạn đã tham chiếu nó, tôi giả sử dict!["currently"]! là một cuốn từ điển và do đó bạn có nhiều lựa chọn:

Trước tiên, bạn có thể làm một cái gì đó như thế này:

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

này sẽ cung cấp cho bạn một đối tượng từ điển mà bạn sau đó có thể truy vấn cho các giá trị và do đó bạn có thể nhận được nhiệt độ của bạn như thế này:

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

Hoặc nếu bạn muốn bạn có thể làm điều đó trong dòng:

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

Hy vọng điều này sẽ giúp ích, tôi e rằng tôi không có thời gian để viết một ứng dụng mẫu để kiểm tra nó.

Lưu ý cuối cùng: điều đơn giản nhất để làm, có thể đơn giản là truyền tải trọng tải JSON vào [String: AnyObject] ngay khi bắt đầu.

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

'dict! [" Hiện tại "]! như! [Chuỗi: Chuỗi] 'sẽ bị lỗi – vadian

+0

Điểm mà tại đó nó bị lỗi là [" nhiệt độ "] !! và cũng cố gắng truyền String thành NSString - đã xác minh giải pháp mới hoạt động http://swiftlang.ng.bluemix.net/#/repl/57d3bc683a422409bf36c391 – discorevilo

+0

'[String: String]' không thể hoạt động vì có một vài số giá trị. Nó không hoạt động trong một ** ** ** ** Sân chơi – vadian

2

Cập nhật các isConnectToNetwork-Function sau đó, nhờ bài này Check for internet connection with Swift

tôi đã viết một phương pháp bổ sung cho nó:

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 

} 

Vì vậy, bây giờ bạn có thể dễ dàng gọi này trong thư mục ứng dụng ở bất cứ nơi nào bạn muốn

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 
       } 
    } 
+0

Marco, cách tốt nhất để thêm chức năng này vào dự án là gì? trong bất kỳ contorller hoặc mô hình? – KamalPanhwar

+0

này, bạn chỉ cần thêm phương thức đầu tiên func loadingJSON (...) vào một tệp hoặc lớp bổ sung nhanh. Sau đó bạn có thể gọi điều này từ mọi bộ điều khiển trong dự án của bạn –

+0

tôi đã thử nó, nhưng tôi thích ý tưởng thể hiện một giải pháp đầy đủ và cách sử dụng nó bao gồm phương thức trợ giúp làConnectedToNetwork() . để thực hiện nó có lẽ trong mã tốt –

5
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)") 
} 
0

Tôi đã tạo quicktype chính xác cho mục đích này. Chỉ cần dán JSON mẫu của bạn và quicktype tạo phân cấp kiểu này cho dữ liệu API của bạn:

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 
} 

Nó cũng tạo ra mã marshaling phụ thuộc miễn phí để dỗ các giá trị trở lại của JSONSerialization.jsonObject thành một Forecast, bao gồm một constructor thuận tiện mà phải mất một JSON chuỗi, do đó bạn có thể nhanh chóng phân tích một mạnh mẽ gõ Forecast giá trị và tiếp cận lĩnh vực của mình:

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

bạn có thể cài đặt quicktype từ NPM với npm i -g quicktype hoặc use the web UI để lấy mã hoàn chỉnh được tạo ra để dán vào sân chơi của bạn.

+0

Các liên kết không thành công. –

+0

Rất tiếc! Đó là https: //app.quicktype.io#l=swift –– sửa lỗi ngay bây giờ. –

0

Vấn đề là với phương pháp tương tác API. Việc phân tích cú pháp JSON chỉ được thay đổi trong Cú pháp. Vấn đề chính là với cách lấy dữ liệu. Những gì bạn đang sử dụng là một cách đồng bộ để nhận dữ liệu. Điều này không hoạt động trong mọi trường hợp. Những gì bạn nên sử dụng là một cách không đồng bộ để Tìm nạp dữ liệu. Bằng cách này, bạn phải yêu cầu dữ liệu thông qua API và chờ cho nó phản hồi dữ liệu. Bạn có thể đạt được điều này với phiên URL và thư viện của bên thứ ba như Alamofire. Dưới đây là phương pháp Mã cho phiên URL.

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() 
Các vấn đề liên quan