解码JSON对象(使用参数发送GET请求) - Swift 4 Decodable

时间:2017-10-24 16:16:31

标签: json swift http-headers http-get swift4

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

我有一些嵌套的JSON对象,我试图解码。

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

   ]
}

^ establishments数组包含Establishment个对象

我的代码成功运行到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对象的输出是:summots wrong - 在ViewController.swift的第11行。

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

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

但我不知道我的班级结构/我在视图控制器中做的事情是否符合此布局标准。

有人能发现我出错的地方吗?我认为这是一个非常基本的东西,但我似乎无法理解它

我也明白,我不应该用大写字母命名类的属性 - 为违反任何惯例而道歉

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

代替: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时,我使用默认为XML格式的API。

我使用推荐的//print("test")

而不是ViewController.swift文件中的print(String(data: data, encoding: .utf8))

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

我有什么方法可以使用此网址发送标题吗?

编辑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标头值为2。

有什么想法吗?

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

我现在面临着班级宣言的问题,特别是在我的“元”杂志中。类。

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

它抱怨,因为我的类声明与它对JSON对象期望的数据不匹配。

在我的JSON响应中(参考要点),有两个meta个对象,一个包含null的值dataSource,另一个包含Lucene。< / p>

我有什么方法可以接受&#39; null&#39;我的类初始化器中的值和字符串,同时保持符合Decodable协议?

JSON响应的那部分对我来说已经死了,我是否还需要为它创建对象,还是我可以忽略它们而逃脱? - 没有声明任何事情。或者我是否需要记下JSON响应中的所有内容才能使用数据?

2 个答案:

答案 0 :(得分: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重新定义键。

答案 1 :(得分: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
        }
    }

希望它能奏效。