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에서 모든 var
iable은 기본적으로 변경 가능하며 이러한 옵션 중 하나를 전달하고 결과를 let
상수에 할당 해도 전혀 효과가 없습니다. 더 나아가 대부분의 구현은 어쨌든 역 직렬화 된 JSON을 변경하지 않습니다.
Swift에서 유용한 유일한 (희귀 한) 옵션 .allowFragments
은 JSON 루트 객체가 컬렉션 유형 ( 또는 ) 중 하나가 아닌 값 유형 ( , 또는 ) String
일 수있는 경우에 필요한 옵션입니다 . 그러나 일반적으로 옵션 없음 을 의미 하는 매개 변수를 생략합니다 .Number
Bool
null
array
dictionary
options
================================================ =========================
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
정수로 디코딩 될 수Date
와dateDecodingStrategy
.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
으로 동축하는 종속성없는 마샬링 코드를 생성 합니다.Forecast
Forecast
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”상용구를 제거하고 기능적 접근 방식을 사용하여 강제로 언 래핑합니다.
- 여기 JSON이 있습니다. 선택적 JSON 또는 평소를 사용할 수 있습니다. 이 예제에서는 선택 사항을 사용하고 있습니다.
let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
- 도우미 기능. 한 번만 작성하고 사전에 재사용하면됩니다.
/// 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
}
- 그리고 여기에 우리의 마법이 있습니다-가치 추출 :
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)")
}