如何使用Decodable解码JSON数组?

6

我希望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解析非常陌生。


2
不要使用 try? 抛弃解码错误,而是捕获错误并将其打印出来;它会告诉你问题的具体所在。 - Hamish
2
虽然与此无关,但在Swift中,类型名称应为UpperCamelCase,而属性和变量名称应为lowerCamelCase(从不使用snake_case)。如果您的JSON键名不同,您可以定义一个自定义的CodingKeys枚举。此外,所有属性都需要是可选的吗?如果给定的键值在JSON中始终存在,则将其设置为非可选项。 - Hamish
@Hamish,感谢您的评论。我已经改变了我的结构,但是我不知道如何捕捉错误。 - Danial Kosarifa
2个回答

11

请捕获错误并阅读错误信息

类型“String”不匹配。
调试描述:期望解码为字符串,但实际上找到了一个数字。

对于challenge_idchallenge_pts_earnedchallenge_category_idstatus_id,您会收到此错误,因为这些值是Int(实际上在读取JSON时就可以注意到这一点)

其次,Date值无法解码,因为您没有提供日期策略(默认值为TimeInterval)。您必须提供自定义日期格式化程序以解码带有分数秒的ISO8601。

最后,如评论中所述,请使用驼峰式变量名称通过指定CodingKeys,并且由于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编码/解码时,强烈建议使用完整的错误处理。这将使调试变得更加容易。

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 

0
你的代码中有几个错误。首先,你的一些变量类型是错误的。所有的 `id` 和 `challenge_pts_earned` 都需要是 `Int` 类型。你可以很容易地发现这一点,因为在 JSON 中,字符串需要用引号括起来。在解码 `Date` 时,你还需要指定使用哪种日期格式。
`startDate` 和 `endDate` 也是日期,所以即使它们可以作为 `String` 解码,我建议你将它们作为实际的 `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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接