2016-09-10 2 views
90

JSON 응답을 가져 와서 결과를 변수에 저장하려고합니다. Xcode 8의 GM 버전이 출시 될 때까지는 이전 버전의 Swift에서이 코드의 버전을 사용할 수있었습니다. StackOverflow에서 유사한 게시물 몇 개를 보았습니다 : Swift 2 Parsing JSON - Cannot subscript a value of type 'AnyObject'JSON Parsing in Swift 3.Swift에서 JSON을 올바르게 구문 분석 3

그러나 여기에 전달 된 아이디어가이 시나리오에 적용되지 않는 것 같습니다.

Swift 3에서 JSON 응답을 올바르게 구문 분석하려면 어떻게해야합니까? Swift 3에서 JSON이 읽히는 방식이 변경 되었습니까?

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

편집 : 아래

은 (는 놀이터에서 실행할 수 있습니다) 문제의 코드 여기는 API 호출의 결과의 샘플입니다 후 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

API 호출에서 반환 된 샘플 데이터를 넣을 수 있습니까? – User

+1

예, 방금 print (currentConditions) 후에 인쇄 된 결과 샘플을 추가했습니다. 희망이 도움이됩니다. – user2563039

답변

139

우선 은 원격 URL에서 데이터를 동 기적으로로드하지 않으므로 항상 URLSession과 같은 비동기 메소드를 사용하십시오.

'모든'는 컴파일러가 중간 오브젝트를 입력 한 내용의 아무 생각이 없기 때문에

이 ( ["currently"]!["temperature"]currently에 대한) 및 이후 당신이 NSDictionary 같은 재단 수집 유형을 사용하는 발생에는 첨자 회원이 없습니다 컴파일러는 타입에 대해 전혀 모른다.

또한 Swift 3에서는 의 모든 유형이 인 하위 개체를 컴파일러에 알려야합니다.

JSON 직렬화의 결과를 실제 유형으로 캐스트해야합니다.

이 코드는 jsonObject(with data에 대한

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

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

메모를 작성할 수 currentConditions의 모든 키/값 쌍을 인쇄하려면 URLSession독점적으로 스위프트 기본 유형

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() 

사용

많은 (모두 보이는 것) 튜토리얼은 .mutableContainers 또는 .mutableLeaves 옵션을 제안합니다.이 옵션은 Swift에서 완전히 의미가 없습니다. 두 가지 옵션은 결과를 NSMutable... 개체에 할당하는 기존 Objective-C 옵션입니다. Swift에서 어떤 var iable도 기본적으로 변경할 수 있으며 그 중 하나를 전달하고 결과를 let 상수에 할당해도 아무 효과가 없습니다. 어쨌든 구현의 대부분은 결코 직렬화되지 않은 JSON을 변형시키지 않습니다.스위프트에 유용하다

유일한 (희귀) 옵션은 JSON 루트 객체가 값 형식 (String, Number, Bool 또는 null)보다는 수집 유형 중 하나가 될 수 있다면 경우에 필요합니다 .allowFragments (array 또는이다 dictionary). 그러나 일반적으로 옵션 없음 의미없는 options 매개 변수를 생략합니다.

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

일부 일반적인 고려 JSON

JSON이 잘 배치 텍스트 인 구문 분석 체재. JSON 문자열을 읽는 것은 매우 쉽습니다. 문자열을주의 깊게을 읽어보십시오. 두 가지 컬렉션 유형과 네 가지 값 유형의 여섯 가지 유형 만 있습니다.


컬렉션 유형은

  • 배열 - JSON : 대괄호 []에서 개체 - 스위프트 : [Any]하지만 대부분의 경우 [[String:Any]]
  • 사전 - JSON : 중괄호의 객체 {} - 스위프트 : [String:Any]
따옴표 "Foo", 심지어 "123" 또는 "false"의 값 - 스위프트 : JSON -개

값 유형은

  • 문자열 있습니다 String
  • - JSON : 숫자 값 번에하지 따옴표 123 또는 123.0 - 스위프트 : Int 또는 Double
  • BOOL - JSON : 따옴표로 true 또는 false하지 - 스위프트 : true 또는 false
  • - JSON : - : 사전에있는 모든 키를 JSON 사양에 따라 NSNull

스위프트 nullString이 요구된다.


기본적으로 항상 루트 개체는 사전 ({}) [String:Any]

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

를 타입 캐스팅의 경우 안전

을 선택적 항목을 푸는 옵션 바인딩을 사용하여 값을 검색 할 권장 사용 것 (OneOfSupportedJSONTypes) 키는 위에서 설명한 JSON 컬렉션 또는 값 유형입니다.

0 루트 개체는 특정의 항목을해야하는 경우 배열 ( []가)

for item in parsedData { 
    print(item) 
} 

으로 배열을 통해 [[String:Any]]

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

및 반복 유형을 주조 경우

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

인덱스가 존재하는지를 확인한다.

보다는 컬렉션 유형 - - json으로 단순히 값 유형 중 하나입니다 드문 경우

당신이 .allowFragments 옵션을 통과하고, 예를 들어 적절한 값 형식으로 결과를 캐스팅해야

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

애플이 스위프트 블로그 포괄적 인 기사 발표했습니다

: Working with JSON in Swift

+0

이것은 매우 유용합니다. 놀이터에서 실행될 때 위 코드가 표시되는 출력을 표시하지 않는 이유가 궁금합니다. – user2563039

+0

위에서 언급했듯이 컴파일러가 후속 키 구독을 안전하게 추론 할 수 있도록 'dict! [ "currently"]! "의 모호한 결과를 사전에 캐스팅해야합니다. – vadian

+1

Apple의 기사는 매우 멋지지만, 어떤 이유로 든 신속하게 실행할 수는 없습니다. Type [String : Any]에 대해 불평합니까? 아래 첨자 멤버가 없습니다. 다른 문제도 있었지만 그 문제를 해결할 수있었습니다. 누구나 실제로 실행되는 코드 예제가 있습니까? – Shades

12

큰 변화 스위프트 3 엑스 코드 8 베타 6 일이 ID가 AnyObject이 아닌 Any으로 가져 오게되었습니다.

즉 의 가능성이 가장 높은 사전으로 parsedData이 반환됩니다. 디버거를 사용하지 않고 정확히 NSDictionary의 캐스트가 무엇을할지 말할 수는 없지만 dict!["currently"]! 유형이 있기 때문에 Any

이 문제를 어떻게 해결합니까? 당신이 그것을 언급 한 방법에서, 나는 dict!["currently"]!가 사전 가정하고 그래서 당신은 많은 옵션이 있습니다

먼저 당신이 이런 일을 할 수있는을 :

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

이 당신에게 사전 객체를 줄 것이다 당신을 다음 값을 조회 할 수 있습니다 그래서 당신은 다음과 같이 당신의 온도를 얻을 수 있습니다 :

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

을 또는 당신이 선호하는 경우 라인에서 작업을 수행 할 수 있습니다

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

잘하면이 테스트를 위해 샘플 앱을 작성할 시간이 없었기를 바랍니다.

최종 메모 : 가장 쉬운 방법은 시작시 JSON 페이로드를 [String: AnyObject]으로 전송하는 것입니다.

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

'dict! [ "현재"]! 같이! [String : String]'이 충돌합니다. – vadian

+0

추락 한 지점이 [ "온도"] !! 문자열을 NSString으로 변환하려고 시도합니다 - 새로운 솔루션이 작동하는지 확인 http://swiftlang.ng.bluemix.net/#/repl/57d3bc683a422409bf36c391 – discorevilo

+0

'[String : String]'은 숫자가 몇 개 있기 때문에 전혀 작동하지 않습니다. 값. ** Mac ** 놀이터에서 작동하지 않습니다 – vadian

2

내가 그것을위한 별도의 방법을 썼다 Check for internet connection with Swift

이 게시물에, 나중에 감사를 isConnectToNetwork-기능을 업데이트 : 그래서 지금 당신은 쉽게이를 호출 할 수 있습니다

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 

} 

당신의 원하는 위치에 앱 추가

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, 프로젝트에서이 함수를 추가하는 가장 좋은 방법은 무엇입니까? 어떤 contorller 또는 모형에서? – KamalPanhwar

+0

이봐, 당신은 여분의 신속한 파일이나 클래스에 첫 번째 메소드 func loadingJSON (...)을 추가하기 만하면된다. 그 후 당신은 프로젝트의 모든 컨트롤러에서 이것을 호출 할 수 있습니다. –

+0

시도해 보았습니다. 그러나 전체 솔루션을 시연하는 아이디어와 도우미 메서드 isConnectedToNetwork()를 포함하여이를 사용하는 방법을 좋아합니다. 어떻게 저의 아이디어입니까? 그것을 좋은 코드 –

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

이 목적으로 정확하게 quicktype을 만들었습니다. 그냥 샘플 JSON을 붙여 quicktype은 API 데이터를이 유형 계층 구조를 생성합니다

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 
} 

그것은 또한 JSON 소요 간이 생성자를 포함 ForecastJSONSerialization.jsonObject의 반환 값을 동축 케이블하기 의존성이없는 마샬링 코드를 생성 문자열 빠르게 강력한 형식의 Forecast 값을 구문 분석하고 해당 필드에 액세스 할 수 있도록 : 당신은 당신의 놀이터에 붙여 전체 생성 된 코드를 얻을 수 npm i -g quicktype 또는 use the web UI과 NPM에서 quicktype를 설치할 수 있습니다

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

합니다.

+0

링크가 실패하고 있습니다. –

+0

죄송합니다. 그것은 https : //app.quicktype.io#l=swift- fixing now입니다. –

0

문제는 API 상호 작용 방식에서 발생합니다. JSON 구문 분석은 구문에서만 변경됩니다. 가장 큰 문제는 데이터를 가져 오는 방법입니다. 당신이 사용하는 것은 데이터를 가져 오는 동기식 방법입니다. 이것은 모든 경우에 적용되지는 않습니다. 사용해야하는 것은 비동기 적으로 데이터를 가져 오는 방법입니다. 이런 식으로 API를 통해 데이터를 요청하고 데이터로 응답 할 때까지 기다려야합니다. Alamofire와 같은 타사 라이브러리 및 URL 세션을 통해이를 수행 할 수 있습니다. 다음은 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()