如何使用 Decodable 解码 JSON 数组?
How to decode a JSON array using Decodable?
我请求 API 向我发送一些数据,我可以成功检索这些数据,但我在解码过程中卡住了。
这是我收到的 JSON:
[
{
"challenge_id":1,
"challenge_title":"newchallenge1",
"challenge_pts_earned":1000,
"challenge_description":"description1",
"start_date":"2017-09-24T00:00:00.000Z",
"end_date":"2017-09-24T00:00:00.000Z",
"challenge_category_id":1,
"status_id":2,
"createdAt":"2017-09-24T17:21:47.000Z",
"updatedAt":"2017-09-24T09:40:34.000Z"
},
{
"challenge_id":2,
"challenge_title":"challenge1",
"challenge_pts_earned":100,
"challenge_description":"description1",
"start_date":"2017-09-24T00:00:00.000Z",
"end_date":"2017-09-24T00:00:00.000Z",
"challenge_category_id":1,
"status_id":0,
"createdAt":"2017-09-24T17:22:12.000Z",
"updatedAt":"2017-09-24T09:22:12.000Z"
},
{
"challenge_id":3,
"challenge_title":"new eat title",
"challenge_pts_earned":600000,
"challenge_description":"haha",
"start_date":"2017-01-09T00:00:00.000Z",
"end_date":"2017-01-10T00:00:00.000Z",
"challenge_category_id":2,
"status_id":0,
"createdAt":"2017-09-27T17:12:10.000Z",
"updatedAt":"2017-09-27T09:15:19.000Z"
}
]
我正在尝试创建以下结构来对其进行解码:
struct challenge : Codable {
let id : String?
let title : String?
let pointsEarned : String?
let description : String?
let dayStarted : String?
let dayEnded : String?
let categoryID : String?
let statusID : Int?
let createdAt : Date?
let updatedAt : Date?
enum CodingKeys: String, CodingKey {
case id = "challenge_id"
case title = "challenge_title"
case pointsEarned = "challenge_pts_earned"
case description = "challenge_description"
case dayStarted = "start_date"
case dayEnded = "end_date"
case categoryID = "challenge_category_id"
case statusID = "status_id"
case createdAt, updatedAt
}
}
这是我的实现代码:
var All_challenges : [challenge]?
let url = URL(string: API.all_challenges.rawValue)!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("\(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
guard let result = responseString else { return }
print(result)
if let json = try? JSONDecoder().decode([challenge].self , from : data ) {
self.All_challenges = json
}
}
task.resume()
然而,当我尝试调试它时,我永远无法输入
的 if 语句
if let json = try? JSONDecoder().decode([challenge].self,from:data ) {
self.All_challenges = json
}
请描述一下我的错误在哪里,我对 JSON 配对
很陌生
请抓住错误并阅读
Type 'String' mismatch.
Debug Description: Expected to decode String but found a number instead.
你得到 challenge_id
、challenge_pts_earned
、challenge_category_id
和 status_id
的错误,因为值是 Int
(实际上你可以注意到当正在阅读 JSON)
其次,Date
值无法解码,因为您没有提供日期策略(默认为 TimeInterval
)。您必须提供自定义日期格式化程序才能用小数秒解码 ISO8601。
最后,如评论中所述,通过指定 CodingKeys
使用 camelCased 变量名,并且 JSON 始终包含所有键,将属性声明为非-可选
let jsonString = """
[
{
"challenge_id":1,
"challenge_title":"newchallenge1",
"challenge_pts_earned":1000,
"challenge_description":"description1",
"start_date":"2017-09-24T00:00:00.000Z",
"end_date":"2017-09-24T00:00:00.000Z",
"challenge_category_id":1,
"status_id":2,
"createdAt":"2017-09-24T17:21:47.000Z",
"updatedAt":"2017-09-24T09:40:34.000Z"
},
{
"challenge_id":2,
"challenge_title":"challenge1",
"challenge_pts_earned":100,
"challenge_description":"description1",
"start_date":"2017-09-24T00:00:00.000Z",
"end_date":"2017-09-24T00:00:00.000Z",
"challenge_category_id":1,
"status_id":0,
"createdAt":"2017-09-24T17:22:12.000Z",
"updatedAt":"2017-09-24T09:22:12.000Z"
},
{
"challenge_id":3,
"challenge_title":"new eat title",
"challenge_pts_earned":600000,
"challenge_description":"haha",
"start_date":"2017-01-09T00:00:00.000Z",
"end_date":"2017-01-10T00:00:00.000Z",
"challenge_category_id":2,
"status_id":0,
"createdAt":"2017-09-27T17:12:10.000Z",
"updatedAt":"2017-09-27T09:15:19.000Z"
}
]
"""
struct Challenge : Decodable {
// private enum CodingKeys : String, CodingKey {
// case challengeId = "challenge_id"
// case challengeTitle = "challenge_title"
// case challengePtsEarned = "challenge_pts_earned"
// case challengeDescription = "challenge_description"
// case startDate = "start_date"
// case endDate = "end_date"
// case challengeCategoryId = "challenge_category_id"
// case statusId = "status_id"
// case createdAt, updatedAt
// }
let challengeId : Int
let challengeTitle : String
let challengePtsEarned : Int
let challengeDescription : String
let startDate : String // or Date
let endDate : String // or Date
let challengeCategoryId : Int
let statusId : Int
let createdAt : Date
let updatedAt : Date
}
let data = Data(jsonString.utf8)
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let challenges = try decoder.decode([Challenge].self, from: data)
print(challenges)
} catch { print(error) }
注:
在开发 JSON en-/decoding 时,强烈建议使用全套错误处理。它使调试变得更加容易
do {
try JSONDecoder().decode ...
} catch DecodingError.dataCorrupted(let context) {
print(context)
} catch DecodingError.keyNotFound(let key, let context) {
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.valueNotFound(let value, let context) {
print("Value '\(value)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.typeMismatch(let type, let context) {
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print("error: ", error)
}
编辑
在 Swift 4.1 及更高版本中,您可以通过添加 keyDecodingStrategy
来省略 CodingKeys
decoder.keyDecodingStrategy = .convertFromSnakeCase
您的代码中有几个错误。首先,您的某些变量类型是错误的。所有 id
和 challenge_pts_earned
都必须是 Int
类型。您可以很容易地发现这一点,因为在 JSON 中,字符串需要用引号分隔。解码Date
s时,还需要指定使用什么日期格式。
startDate
和 endDate
也是日期,所以即使它们可以被解码为 String
s,我还是建议您将它们存储为实际的 Date
对象.
还请遵守 Swift 命名约定,即类型为 upperCamelCase,变量和函数名称为 lowerCamelCase。您可以使用符合 CodingKey
.
的自定义类型将自定义变量名称映射到它们的 JSON 表示
struct Challenge : Decodable {
let id: Int?
let title: String?
let pointsEarned: Int?
let description: String?
let startDate: String?
let endDate: String?
let categoryId: Int?
let statusId: Int?
let createdAt: Date?
let updatedAt: Date?
private enum CodingKeys: String, CodingKey {
case id = "challenge_id"
case title = "challenge_title"
case pointsEarned = "challenge_pts_earned"
case description = "challenge_description"
case startDate = "start_date"
case endDate = "end_date"
case categoryId = "challenge_category_id"
case statusId = "status_id"
case createdAt
case updatedAt
}
static var dateFormatter: DateFormatter {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
df.locale = Locale(identifier: "en_US_POSIX")
return df
}
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Challenge.dateFormatter)
let decoded = try decoder.decode([Challenge].self, from: jsonArrayResponse.data(using: .utf8)!)
} catch {
print(error)
}
我请求 API 向我发送一些数据,我可以成功检索这些数据,但我在解码过程中卡住了。 这是我收到的 JSON:
[
{
"challenge_id":1,
"challenge_title":"newchallenge1",
"challenge_pts_earned":1000,
"challenge_description":"description1",
"start_date":"2017-09-24T00:00:00.000Z",
"end_date":"2017-09-24T00:00:00.000Z",
"challenge_category_id":1,
"status_id":2,
"createdAt":"2017-09-24T17:21:47.000Z",
"updatedAt":"2017-09-24T09:40:34.000Z"
},
{
"challenge_id":2,
"challenge_title":"challenge1",
"challenge_pts_earned":100,
"challenge_description":"description1",
"start_date":"2017-09-24T00:00:00.000Z",
"end_date":"2017-09-24T00:00:00.000Z",
"challenge_category_id":1,
"status_id":0,
"createdAt":"2017-09-24T17:22:12.000Z",
"updatedAt":"2017-09-24T09:22:12.000Z"
},
{
"challenge_id":3,
"challenge_title":"new eat title",
"challenge_pts_earned":600000,
"challenge_description":"haha",
"start_date":"2017-01-09T00:00:00.000Z",
"end_date":"2017-01-10T00:00:00.000Z",
"challenge_category_id":2,
"status_id":0,
"createdAt":"2017-09-27T17:12:10.000Z",
"updatedAt":"2017-09-27T09:15:19.000Z"
}
]
我正在尝试创建以下结构来对其进行解码:
struct challenge : Codable {
let id : String?
let title : String?
let pointsEarned : String?
let description : String?
let dayStarted : String?
let dayEnded : String?
let categoryID : String?
let statusID : Int?
let createdAt : Date?
let updatedAt : Date?
enum CodingKeys: String, CodingKey {
case id = "challenge_id"
case title = "challenge_title"
case pointsEarned = "challenge_pts_earned"
case description = "challenge_description"
case dayStarted = "start_date"
case dayEnded = "end_date"
case categoryID = "challenge_category_id"
case statusID = "status_id"
case createdAt, updatedAt
}
}
这是我的实现代码:
var All_challenges : [challenge]?
let url = URL(string: API.all_challenges.rawValue)!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("\(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
guard let result = responseString else { return }
print(result)
if let json = try? JSONDecoder().decode([challenge].self , from : data ) {
self.All_challenges = json
}
}
task.resume()
然而,当我尝试调试它时,我永远无法输入
的 if 语句if let json = try? JSONDecoder().decode([challenge].self,from:data ) {
self.All_challenges = json
}
请描述一下我的错误在哪里,我对 JSON 配对
很陌生请抓住错误并阅读
Type 'String' mismatch.
Debug Description: Expected to decode String but found a number instead.
你得到 challenge_id
、challenge_pts_earned
、challenge_category_id
和 status_id
的错误,因为值是 Int
(实际上你可以注意到当正在阅读 JSON)
其次,Date
值无法解码,因为您没有提供日期策略(默认为 TimeInterval
)。您必须提供自定义日期格式化程序才能用小数秒解码 ISO8601。
最后,如评论中所述,通过指定 CodingKeys
使用 camelCased 变量名,并且 JSON 始终包含所有键,将属性声明为非-可选
let jsonString = """
[
{
"challenge_id":1,
"challenge_title":"newchallenge1",
"challenge_pts_earned":1000,
"challenge_description":"description1",
"start_date":"2017-09-24T00:00:00.000Z",
"end_date":"2017-09-24T00:00:00.000Z",
"challenge_category_id":1,
"status_id":2,
"createdAt":"2017-09-24T17:21:47.000Z",
"updatedAt":"2017-09-24T09:40:34.000Z"
},
{
"challenge_id":2,
"challenge_title":"challenge1",
"challenge_pts_earned":100,
"challenge_description":"description1",
"start_date":"2017-09-24T00:00:00.000Z",
"end_date":"2017-09-24T00:00:00.000Z",
"challenge_category_id":1,
"status_id":0,
"createdAt":"2017-09-24T17:22:12.000Z",
"updatedAt":"2017-09-24T09:22:12.000Z"
},
{
"challenge_id":3,
"challenge_title":"new eat title",
"challenge_pts_earned":600000,
"challenge_description":"haha",
"start_date":"2017-01-09T00:00:00.000Z",
"end_date":"2017-01-10T00:00:00.000Z",
"challenge_category_id":2,
"status_id":0,
"createdAt":"2017-09-27T17:12:10.000Z",
"updatedAt":"2017-09-27T09:15:19.000Z"
}
]
"""
struct Challenge : Decodable {
// private enum CodingKeys : String, CodingKey {
// case challengeId = "challenge_id"
// case challengeTitle = "challenge_title"
// case challengePtsEarned = "challenge_pts_earned"
// case challengeDescription = "challenge_description"
// case startDate = "start_date"
// case endDate = "end_date"
// case challengeCategoryId = "challenge_category_id"
// case statusId = "status_id"
// case createdAt, updatedAt
// }
let challengeId : Int
let challengeTitle : String
let challengePtsEarned : Int
let challengeDescription : String
let startDate : String // or Date
let endDate : String // or Date
let challengeCategoryId : Int
let statusId : Int
let createdAt : Date
let updatedAt : Date
}
let data = Data(jsonString.utf8)
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let challenges = try decoder.decode([Challenge].self, from: data)
print(challenges)
} catch { print(error) }
注:
在开发 JSON en-/decoding 时,强烈建议使用全套错误处理。它使调试变得更加容易
do {
try JSONDecoder().decode ...
} catch DecodingError.dataCorrupted(let context) {
print(context)
} catch DecodingError.keyNotFound(let key, let context) {
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.valueNotFound(let value, let context) {
print("Value '\(value)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.typeMismatch(let type, let context) {
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print("error: ", error)
}
编辑
在 Swift 4.1 及更高版本中,您可以通过添加 keyDecodingStrategy
decoder.keyDecodingStrategy = .convertFromSnakeCase
您的代码中有几个错误。首先,您的某些变量类型是错误的。所有 id
和 challenge_pts_earned
都必须是 Int
类型。您可以很容易地发现这一点,因为在 JSON 中,字符串需要用引号分隔。解码Date
s时,还需要指定使用什么日期格式。
startDate
和 endDate
也是日期,所以即使它们可以被解码为 String
s,我还是建议您将它们存储为实际的 Date
对象.
还请遵守 Swift 命名约定,即类型为 upperCamelCase,变量和函数名称为 lowerCamelCase。您可以使用符合 CodingKey
.
struct Challenge : Decodable {
let id: Int?
let title: String?
let pointsEarned: Int?
let description: String?
let startDate: String?
let endDate: String?
let categoryId: Int?
let statusId: Int?
let createdAt: Date?
let updatedAt: Date?
private enum CodingKeys: String, CodingKey {
case id = "challenge_id"
case title = "challenge_title"
case pointsEarned = "challenge_pts_earned"
case description = "challenge_description"
case startDate = "start_date"
case endDate = "end_date"
case categoryId = "challenge_category_id"
case statusId = "status_id"
case createdAt
case updatedAt
}
static var dateFormatter: DateFormatter {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
df.locale = Locale(identifier: "en_US_POSIX")
return df
}
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Challenge.dateFormatter)
let decoded = try decoder.decode([Challenge].self, from: jsonArrayResponse.data(using: .utf8)!)
} catch {
print(error)
}