2017-10-24 4 views
1

다음 요점을 고려하면 : https://gist.github.com/anonymous/0703a591f97fa9f6ad35b29234805fbd - 파일이 상당히 길기 때문에 모든 관련 정보를 효율적으로 표시 할 수 없습니다. 사과JSON 객체 디코딩 (매개 변수가 포함 된 GET 요청 전송) - Swift 4 디코딩 가능

디코딩하려고하는 일부 중첩 된 JSON 개체가 있습니다.

{ 
    "establishments":[ 
     { ... } 
    ], 
    "meta":{ ... }, 
    "links":[ 

    ] 
} 

는^establishments 배열

내 코드가 성공적으로 실제로 스위프트 4의 JSONDecode() 기능을 사용하고있어 ViewController.swift의 7 행까지 실행 Establishment 객체

가 포함되어 있습니다.

ViewController.swift

let jsonURLString = "http://api.ratings.food.gov.uk/Establishments?address=\(self.getPostalCode(place: place))&latitude=\(place.coordinate.latitude.description)&longitude=\(place.coordinate.longitude.description)&maxDistanceLimit=0&name=\(truncatedEstablishmentName[0].replacingOccurrences(of: "'", with: ""))" 

guard let url = URL(string: jsonURLString) else { print("URL is invalid"); return } 

URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in 
    guard let data = data, error == nil, response != nil else { 
     print("Something went wrong") 
     return 
    } 
    //print("test") 
    do { 
     let establishments = try JSONDecoder().decode(Establishments.self, from: data) 
     print(establishments) 
    } catch { 
     print("summots wrong") 
    } 

}).resume() 

내가 Establishment.swift에서 본 맵핑 JSON 개체를 얻을 출력은 : summots wrong - ViewController.swift 라인 (11)에.

{ 
    "establishments":[ ... ], 
    "meta":{ ... }, 
    "links":[ ] 
} 

하지만 내 클래스 구조가/내보기 컨트롤러에 무슨 일을했는지 ​​모르겠 :

내가 JSON 형식과 같이하기 때문에 예상대로 작동하지 않는 이유는 생각 이 레이아웃 기준을 만족합니다.

어디에서 내가 잘못 될 지 눈여겨 볼 수 있습니까? 나는 그것이 아주 기본적인 일이라고 생각하지만 난 그것을 주위에 내 머리를 얻이 수없는 것

나는 또한 내가 이름을 대문자로 클래스의 속성에 안거야 이해 - 어떤 규칙

위반에 대한 사과 편집 : 내가 대신 내 자신의 문장의 오류를 인쇄는 다음과 같다 :

대신 : print("summots wrong")

내가 쓴 : print(error)

,
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}))) 

Hamish가 올바르게 지적했듯이 GET 요청에서 올바른 JSON 데이터를받지 못했습니다. 사실 JSON을 전혀받지 못합니다. x-api-version2으로 설정되지 않은 경우 사용하는 API는 XML 형식으로 기본 설정됩니다. 내 JSON 출력을 검색 할 수 x-api-version: 2, accept: application/jsoncontent-type: application/json : 나는 다음과 같은 헤더와 함께 우편 배달을 사용 print(String(data: data, encoding: .utf8)) 라인

: 내 ViewController.swift 파일

대신 //print("test")

, 나는 추천을 사용했다. 브라우저 창에 동일한 GET 문자열 (모든 매개 변수가 올바르게 채워져 있음)을 사용하면 XML 형식의 The API 'Establishments' doesn't exist이라는 오류 메시지가 표시됩니다.

이 URL이 포함 된 헤더를 보낼 수있는 방법이 있습니까?

편집 2 : URL 대신 URLRequest을 사용하도록 요청을 변경했습니다. 여기

내 업데이트됩니다 ViewController.swift 코드 :

let jsonURLString = "http://api.ratings.food.gov.uk/Establishments?address=\(self.getPostalCode(place: place))&latitude=\(place.coordinate.latitude.description)&longitude=\(place.coordinate.longitude.description)&maxDistanceLimit=0&name=\(truncatedEstablishmentName[0].replacingOccurrences(of: "'", with: ""))" 

guard let url = URL(string: jsonURLString) else { print("URL is invalid"); return } 
var request = URLRequest(url: url) 
request.httpMethod = "GET" 
request.addValue("x-api-version", forHTTPHeaderField: "2") 
request.addValue("accept", forHTTPHeaderField: "application/json") 
request.addValue("content-type", forHTTPHeaderField: "application/json") 

URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in 
    guard let data = data, error == nil, response != nil else { 
     print("Something went wrong") 
     return 
    } 
    print(String(data: data, encoding: .utf8)) 
// do { 
//  let establishments = try JSONDecoder().decode(Establishments.self, from: data) 
//  print(establishments) 
// } catch { 
//  print(error) 
// } 

}).resume() 

I 오류 받고 있어요 : HTTP Error 400. The request has an invalid header name

나는,이 요청이 단지 2

x-api-version 헤더 값을 몇 가지 검사를 무시 않았다을

아이디어가 있으십니까?

편집 3 :addValue에 대한 올바른 형식이 있었어야 : request.addValue("2", forHTTPHeaderField: 'x-api-version 등등.

저는 이제 내 클래스 선언과 관련된 문제, 특히 '메타'클래스 문제에 직면 해 있습니다. 내 클래스 선언이는 JSON 객체에서 기대하는 것 데이터와 일치하지 않기 때문에

Meta.(CodingKeys in _DF4B170746CD5543281B14E0B0E7F6FB).dataSource], debugDescription: "Expected String value but found null instead.", underlyingError: nil

것은이 불평. 제 JSON 응답 (요지 참조) meta 두 개체가에서는

, 하나 dataSource 및 다른 Lucenenull 대한 값을 포함한다.

Decodable 프로토콜을 준수하면서 클래스 초기 자의 문자열뿐만 아니라 'null'값을 허용 할 수있는 방법이 있습니까?

JSON 응답의 해당 부분이 죽었습니다. 여전히 개체를 만들어야하나요, 아니면 무시할 수 있습니까? - 아무것도 선언하지 않고. 아니면 특별히 데이터를 사용할 수 있도록 JSON 응답의 모든 것을 기록해야합니까?

+0

또한 코드의 관련 부분 ([mcve] 선호)을 요점에 연결하는 대신 질문 본문 자체에 복사하여 붙여 넣으십시오. 우리는 귀하의 질문을 이해할 수 있도록이 페이지를 떠나지 않아야합니다. – Hamish

+0

건설적인 비평 주셔서 감사합니다. 질문에 약간의 수정을했습니다. –

+0

JSON 출력을 JSON 검사기를 통해 실행했는데 모든 것이 유효한 것으로 보입니다. 어떤 아이디어? : \ –

답변

1

자동으로 디코딩하려면 모델 클래스 속성이 응답 값 유형과 일치해야합니다. JSON 응답에서 JSON 값 유형과 일치하지 않는 데이터 모델 속성은 거의 없습니다. 예를 들면 :

//inside response 
"Hygiene": 10, // which is integer type 

// properties in Scores 
let Hygiene: String 

어느 쪽이든 당신은 응답 또는 클래스 속성과 같은 JSON 응답으로 모델 클래스 속성을 변경해야합니다.시연을 위해

, 나는 다음과 같이 변경하여 모델 클래스를 수정했습니다

 // changed from String 
     let Hygiene: Int 
     let Structural: Int 
     let ConfidenceInManagement: Int 

     // changed from Double 
     let longitude: String 
     let latitude: String 

     // changed to optional 
     let dataSource: String? 
     let returncode: String? 

     // changed from Int 
     let RatingValue: String 

아래 변경 모델 클래스를 대체합니다

class Scores: Decodable { 
     // set as string because we can expect values such as 'exempt' 
     let Hygiene: Int 
     let Structural: Int 
     let ConfidenceInManagement: Int 

     init(Hygiene: Int, Structural: Int, ConfidenceInManagement: Int) { 
      self.Hygiene = Hygiene 
      self.Structural = Structural 
      self.ConfidenceInManagement = ConfidenceInManagement 
     } 
    } 

    class Geocode: Decodable { 
     let longitude: String 
     let latitude: String 

     init(longitude: String, latitude: String) { 
      self.longitude = longitude 
      self.latitude = latitude 
     } 
    } 

    class Meta: Decodable { 
     let dataSource: String? 
     let extractDate: String 
     let itemCount: Int 
     let returncode: String? 
     let totalCount: Int 
     let totalPages: Int 
     let pageSize: Int 
     let pageNumber: Int 

     init(dataSource: String, extractDate: String, itemCount: Int, returncode: String, totalCount: Int, totalPages: Int, pageSize: Int, pageNumber: Int) { 

      self.dataSource = dataSource 
      self.extractDate = extractDate 
      self.itemCount = itemCount 
      self.returncode = returncode 
      self.totalCount = totalCount 
      self.totalPages = totalPages 
      self.pageSize = pageSize 
      self.pageNumber = pageNumber 
     } 
    } 

    class Establishments: Decodable { 
     let establishments: [Establishment] 

     init(establishments: [Establishment]) { 
      self.establishments = establishments 
     } 
    } 

    class Establishment: Decodable { 
     let FHRSID: Int 
     let LocalAuthorityBusinessID: String 
     let BusinessName: String 
     let BusinessType: String 
     let BusinessTypeID: Int 
     let AddressLine1: String 
     let AddressLine2: String 
     let AddressLine3: String 
     let AddressLine4: String 
     let PostCode: String 
     let Phone: String 
     let RatingValue: String 
     let RatingKey: String 
     let RatingDate: String 
     let LocalAuthorityCode: Int 
     let LocalAuthorityName: String 
     let LocalAuthorityWebSite: String 
     let LocalAuthorityEmailAddress: String 
     let scores: Scores 
     let SchemeType: String 
     let geocode: Geocode 
     let RightToReply: String 
     let Distance: Double 
     let NewRatingPending: Bool 
     let meta: Meta 
     let links: [String] 

     init(FHRSID: Int, LocalAuthorityBusinessID: String, BusinessName: String, BusinessType: String, BusinessTypeID: Int, AddressLine1: String, AddressLine2: String, AddressLine3: String, AddressLine4: String, PostCode: String, Phone: String, RatingValue: String, RatingKey: String, RatingDate: String, LocalAuthorityCode: Int, LocalAuthorityName: String, LocalAuthorityWebSite: String, LocalAuthorityEmailAddress: String, scores: Scores, SchemeType: String, geocode: Geocode, RightToReply: String, Distance: Double, NewRatingPending: Bool, meta: Meta, links: [String]) { 

      self.FHRSID = FHRSID 
      self.LocalAuthorityBusinessID = LocalAuthorityBusinessID 
      self.BusinessName = BusinessName 
      self.BusinessType = BusinessType 
      self.BusinessTypeID = BusinessTypeID 
      self.AddressLine1 = AddressLine1 
      self.AddressLine2 = AddressLine2 
      self.AddressLine3 = AddressLine3 
      self.AddressLine4 = AddressLine4 
      self.PostCode = PostCode 
      self.Phone = Phone 
      self.RatingValue = RatingValue 
      self.RatingKey = RatingKey 
      self.RatingDate = RatingDate 
      self.LocalAuthorityCode = LocalAuthorityCode 
      self.LocalAuthorityName = LocalAuthorityName 
      self.LocalAuthorityWebSite = LocalAuthorityWebSite 
      self.LocalAuthorityEmailAddress = LocalAuthorityEmailAddress 
      self.scores = scores 
      self.SchemeType = SchemeType 
      self.geocode = geocode 
      self.RightToReply = RightToReply 
      self.Distance = Distance 
      self.NewRatingPending = NewRatingPending 
      self.meta = meta 
      self.links = links 
     } 
    } 

가 작동 바랍니다.

+0

감사합니다. 이것은 대단히 도움이되었습니다. 나는 이것을 (이전에 선택적 매개 변수로 추가했지만 분명히 'null'가능한 값 모두가 아닌) 추가하려고 시도했다고 생각했다. –

1

이 코드는 헤더에 올바른 내용 유형이 설정된 Json으로 데이터를 검색해야합니다.

let jsonURLString = "http://api.ratings.food.gov.uk/Establishments?address=\(self.getPostalCode(place: place))&latitude=\(place.coordinate.latitude.description)&longitude=\(place.coordinate.longitude.description)&maxDistanceLimit=0&name=\(truncatedEstablishmentName[0].replacingOccurrences(of: "'", with: ""))" 

guard let url = URL(string: jsonURLString) else { print("URL is invalid"); return } 
var request = URLRequest(url: url) 
request.httpMethod = "GET" 
request.addValue("2", forHTTPHeaderField: "x-api-version") 
request.addValue("application/json", forHTTPHeaderField: "accept") 
request.addValue("application/json", forHTTPHeaderField: "content-type") 

URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in 
    guard let data = data, error == nil, response != nil else { 
     print("Something went wrong") 
     return 
    } 

    do { 
    let establishments = try JSONDecoder().decode(Establishments.self, from: data) 
    print(establishments) 
    } catch { 
    print(error) 
    } 
}).resume() 

그런 다음 null 요구를 할 수있는 모든 속성은 선택 사항으로 선언합니다. 단지 data-source을 감안하면 null이 될 수 있습니다, 당신의 메타 객체 선언은 다음과 같이 될 것입니다 : 최종 참고로

class Meta: Decodable { 
    let dataSource: String? 
    let extractDate: String 
    let itemCount: Int 
    let returncode: String 
    let totalCount: Int 
    let totalPages: Int 
    let pageSize: Int 
    let pageNumber: Int 
} 

, 당신이 모델의 모든 곳에서 속성을 소문자로 한 것을 선호하는 경우, 당신은 overriding the CodingKey enum하여 키를 다시 정의 할 수 있습니다.