Decoding JSON object (发送带参数的GET请求) - Swift 4 Decodable

Decoding JSON object (sending GET request with parameters) - Swift 4 Decodable

考虑以下要点:https://gist.github.com/anonymous/0703a591f97fa9f6ad35b29234805fbd - 没有有效的方法来显示所有相关信息,因为文件相当长。道歉

我有一些嵌套的 JSON object 正在尝试解码。

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

   ]
}

^ establishments 数组包含 Establishment objects

我的代码成功运行到 ViewController.swift 的第 7 行,在那里我实际使用了 Swift 4 的 JSONDecode() 功能。

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 object 的输出是:summots wrong - 在 ViewController.swift 的第 11 行。

我认为它没有按预期工作的原因是因为 JSON 格式是这样的:

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

但我不知道我的 class 结构/我在视图控制器中所做的是否满足此布局标准。

谁能发现我哪里出错了?我认为这是非常基本的东西,但我似乎无法理解它

我也明白我不应该用大写字母命名 classes 的属性 - 对于违反任何约定表示歉意

编辑:我打印了错误而不是我自己的声明,如下所示:

而不是: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-version 未设置为 2 时,我使用的 API 默认为 XML 格式。

我的 ViewController.swift 文件中没有使用 //print("test"),而是使用了推荐的:print(String(data: data, encoding: .utf8))

我将 Postman 与以下 header 一起使用:x-api-version: 2accept: application/jsoncontent-type: application/json 来检索我的 JSON 输出。如果我在浏览器 window 中使用相同的 GET 字符串(所有参数均已正确填写),我将收到一条错误消息:The API 'Establishments' doesn't exist 格式为 XML。

有什么方法可以让我用这个 URL 发送一个 header 吗?

编辑 2:我已将请求更改为使用 URLRequest 而不仅仅是 URL

这是我更新的 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()

我收到错误: HTTP Error 400. The request has an invalid header name

我做了一些检查,请求只是忽略了 x-api-version header 值 2。

有什么想法吗?

编辑 3: addValue 的正确格式应该是: request.addValue("2", forHTTPHeaderField: 'x-api-version 等等。

我现在面临 class 声明的问题,特别是在我的 'meta' class.

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

它在抱怨,因为我的 class 声明与它期望的来自 JSON object 的数据不匹配。

在我的 JSON 回复中(参考要点)有两个 meta object,一个包含 null 的值 dataSource另一个是 Lucene.

有没有什么方法可以让我在我的 class 初始化程序中接受 'null' 值和字符串,同时遵守 Decodable 协议?

JSON 响应的那部分对我来说已经死了,我是否仍需要为其创建 objects,或者我可以忽略它们吗? - 无需声明任何内容。或者我是否特别需要记下 JSON 响应中的所有内容才能使用数据?

此代码应将数据检索为 Json,并在 headers 中设置正确的 content-type。

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的属性都需要声明为optional。考虑到只有 data-source 可以为 null,您的元 object 声明将是这样的:

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 重新定义键。

要自动解码,您的模型 class 属性必须与响应值类型相匹配。从 JSON 响应中,我可以看到很少有数据模型属性与 JSON 值类型不匹配。例如:

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

// properties in Scores
let Hygiene: String

要么您必须将模型 class 属性更改为响应,要么将 JSON 响应更改为您的 class 属性。

为了演示,我修改了您的模型 class 并进行了以下更改:

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

用以下更改替换您的模型 classes:

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

希望它能奏效。