[json] Swift 3에서 JSON을 올바르게 구문 분석

JSON 응답을 가져오고 결과를 변수에 저장하려고합니다. 저는이 코드의 버전이 Xcode 8의 GM 버전이 출시 될 때까지 Swift의 이전 릴리스에서 작동했습니다. I에 유래에 대한 몇 가지 유사한 게시물을 살펴했다 : 스위프트 2 구문 분석 JSON을 – 형 ‘AnyObject’의 값 첨자 할 수 없습니다스위프트 3에서 JSON 구문 분석을 .

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

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]



답변

우선 원격 URL에서 동 기적으로 데이터를로드하지 , 항상 같은 비동기 방법을 사용합니다 URLSession.

‘Any’에는 아래 첨자 멤버가 없습니다.

컴파일러가 중간 오브젝트 (예를 들어 있습니다 입력 한 내용의 아무 생각이 없기 때문에 발생 currently에서 ["currently"]!["temperature"])와 같은 당신이 재단 수집 유형을 사용하기 때문에 NSDictionary컴파일러가 타입에 대해 전혀 모르고있다가.

또한 Swift 3에서는 모든 첨자 된 객체 의 유형에 대해 컴파일러에 알려야 합니다.

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

이 코드 사용 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()

모든 키 / 값 쌍을 인쇄하려면 currentConditions쓸 수 있습니다.

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

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

관련 참고 사항 jsonObject(with data:

많은 (모두 보이는) 튜토리얼 이 Swift에서 완전히 말도 안되는 옵션 .mutableContainers이나 제안을 제안 .mutableLeaves합니다. 두 가지 옵션은 결과를 NSMutable...개체 에 할당하는 레거시 Objective-C 옵션 입니다. Swift에서 모든 variable은 기본적으로 변경 가능하며 이러한 옵션 중 하나를 전달하고 결과를 let상수에 할당 해도 전혀 효과가 없습니다. 더 나아가 대부분의 구현은 어쨌든 역 직렬화 된 JSON을 변경하지 않습니다.

Swift에서 유용한 유일한 (희귀 한) 옵션 .allowFragments은 JSON 루트 객체가 컬렉션 유형 ( 또는 ) 중 하나가 아닌 값 유형 ( , 또는 ) String일 수있는 경우에 필요한 옵션입니다 . 그러나 일반적으로 옵션 없음 을 의미 하는 매개 변수를 생략합니다 .NumberBoolnullarraydictionaryoptions

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

JSON 구문 분석에 대한 몇 가지 일반적인 고려 사항

JSON은 잘 정렬 된 텍스트 형식입니다. JSON 문자열을 읽는 것은 매우 쉽습니다. 문자열을주의 깊게 읽으십시오 . 컬렉션 유형 2 개와 값 유형 4 개 등 6 가지 유형 만 있습니다.


컬렉션 유형은 다음과 같습니다.

  • 배열 -JSON : 대괄호 안의 객체 []-Swift : [Any]그러나 대부분의 경우[[String:Any]]
  • 사전 -JSON : 중괄호 안의 객체 {}-Swift :[String:Any]

값 유형은 다음과 같습니다.

  • 문자열 -JSON : 큰 따옴표로 묶인 모든 값 "Foo", 짝수 "123"또는 "false"– Swift :String
  • 숫자 -JSON : 큰 따옴표가 없는 숫자 값 123또는 123.0– Swift : Int또는Double
  • Bool -JSON : true또는 큰 따옴표로 묶지 false 않음 – Swift : true또는false
  • null -JSON : null– Swift :NSNull

JSON 사양에 따라 사전의 모든 키는 String.


기본적으로 선택적 바인딩을 사용하여 옵션을 안전하게 풀기 위해 항상 권장됩니다.

루트 객체가 사전 ( {})이면 유형을[String:Any]

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

( OneOfSupportedJSONTypes는 위에 설명 된대로 JSON 컬렉션 또는 값 유형입니다.)

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

루트 객체가 배열 ( [])이면 유형을[[String:Any]]

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

다음을 사용하여 배열을 반복합니다.

for item in parsedData {
    print(item)
}

특정 인덱스에 항목이 필요한 경우 인덱스가 있는지도 확인하십시오.

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

드물지만 JSON이 컬렉션 유형이 아니라 값 유형 중 하나 인 경우 .allowFragments옵션 을 전달 하고 결과를 적절한 값 유형으로 캐스팅해야합니다.

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

Apple은 Swift Blog : Working with JSON in Swift에 포괄적 인 기사를 게시했습니다.


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

Swift 4+에서 Codable프로토콜은 JSON을 구조체 / 클래스로 직접 구문 분석하는보다 편리한 방법을 제공합니다.

예를 들어 질문에 제공된 JSON 샘플 (약간 수정 됨)

let jsonString = """
{"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460}
"""

struct로 디코딩 할 수 있습니다 Weather. Swift 유형은 위에서 설명한 것과 동일합니다. 몇 가지 추가 옵션이 있습니다.

  • 를 나타내는 문자열 URL로 직접 디코딩 할 수있다 URL.
  • time정수로 디코딩 될 수 DatedateDecodingStrategy .secondsSince1970.
  • snaked_cased JSON 키는 다음을 사용하여 camelCase 로 변환 할 수 있습니다 .keyDecodingStrategy .convertFromSnakeCase

struct Weather: Decodable {
    let icon, summary: String
    let pressure: Double, humidity, windSpeed : Double
    let ozone, temperature, dewPoint, cloudCover: Double
    let precipProbability, precipIntensity, apparentTemperature, windBearing : Int
    let time: Date
}

let data = Data(jsonString.utf8)
do {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .secondsSince1970
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let result = try decoder.decode(Weather.self, from: data)
    print(result)
} catch {
    print(error)
}

기타 코딩 가능한 소스 :


답변

스위프트 3 엑스 코드 8 베타 6에 일어난 큰 변화는 해당 ID가 지금 수입이었다 Any보다는 AnyObject.

이것은 parsedData유형과 가장 가능성이 높은 사전으로 반환 된다는 것을 의미합니다 [Any:Any]. 디버거를 사용하지 않고는 캐스트 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

이것이 도움이되기를 바라며, 테스트 할 샘플 앱을 작성할 시간이 없었습니다.

마지막 메모 : 가장 쉬운 방법 [String: AnyObject]은 처음에 JSON 페이로드를 바로 캐스팅하는 것 입니다.

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


답변

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


답변

저는 이 목적을 위해 정확히 퀵 타입을 만들었 습니다 . 샘플 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 문자열을 사용하는 편의 생성자를 포함하여 의 반환 값을 JSONSerialization.jsonObject으로 동축하는 종속성없는 마샬링 코드를 생성 합니다.ForecastForecast

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

npm에서 quicktype을 설치 npm i -g quicktype하거나 웹 UI사용하여 생성 된 전체 코드를 플레이 그라운드에 붙여 넣을 수 있습니다.


답변

업데이트isConnectToNetwork-Function 이것 이후, 덕분에 게시물을 .

나는 그것에 대한 추가 방법을 썼다.

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


답변

Swift에는 강력한 유형 추론이 있습니다. “if let”또는 “guard let”상용구를 제거하고 기능적 접근 방식을 사용하여 강제로 언 래핑합니다.

  1. 여기 JSON이 있습니다. 선택적 JSON 또는 평소를 사용할 수 있습니다. 이 예제에서는 선택 사항을 사용하고 있습니다.

    let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
  1. 도우미 기능. 한 번만 작성하고 사전에 재사용하면됩니다.

    /// 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. 그리고 여기에 우리의 마법이 있습니다-가치 추출 :

    let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound

한 줄의 코드로 강제 풀림이나 수동 유형 캐스팅이 없습니다. 이 코드는 플레이 그라운드에서 작동하므로 복사하여 확인할 수 있습니다. 다음은 GitHub 의 구현 입니다.


답변

이것은 문제를 해결하는 다른 방법입니다. 따라서 아래 솔루션을 확인하십시오. 도움이되기를 바랍니다.

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