6

I am requesting the API to send me some data which I can successfully retrieve yet I am stuck in the decoding process of it . here is the JSON I receive :

[  
   {  
      "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"
   }
]

and I am trying to create the following structure to decode it :

   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
    }

}

And here is my code for the implementation :

 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()

yet when I try to debug it I can never enter the if statement for

if let json = try? JSONDecoder().decode([challenge].self,from:data ) {
self.All_challenges = json

 }

Please give me some description on where my mistake is , I am very new to JSON paring

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Danial Kosarifa
  • 1,044
  • 4
  • 18
  • 51
  • 2
    Don't throw away the decoding error by using `try?`, instead catch the error and print it out; it'll tell you exactly what the problem is. – Hamish Dec 12 '17 at 12:37
  • 2
    Unrelated, but it's Swift convention for type names to be `UpperCamelCase` and property & variable variables names to be `lowerCamelCase` (never `snake_case`). If the keys in your JSON differ, [you can define a custom `CodingKeys` enum](https://stackoverflow.com/q/44396500/2976878). Also do all your properties *need* to be optional? If a given key-value should always be present in the JSON, make it non-optional. – Hamish Dec 12 '17 at 12:39
  • @Hamish thanks for your comment I have already changed the structure of my structure but I have no idea how to catch the error . – Danial Kosarifa Dec 12 '17 at 13:02

2 Answers2

11

Please catch the error and read it

Type 'String' mismatch.
Debug Description: Expected to decode String but found a number instead.

You get that error for challenge_id, challenge_pts_earned, challenge_category_id and status_id because the values are Int (actually you can notice that already when reading the JSON)

Secondly, the Date values cannot be decoded because you didn't provide a date strategy (the default is TimeInterval). You have to provide a custom date formatter to decode ISO8601 with fractional seconds.

Finally, as mentioned in the comments use camelCased variable names by specifying CodingKeys and as the JSON contains always all keys declare the properties as non-optional

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

Note:

While developing JSON en-/decoding it's highly recommended to use the full set of error handling. It makes debugging so much easier

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

Edit

In Swift 4.1 and higher you can omit the CodingKeys by adding the keyDecodingStrategy

decoder.keyDecodingStrategy = .convertFromSnakeCase 
vadian
  • 274,689
  • 30
  • 353
  • 361
0

You had several errors in your code. First of all, some of the types of your variables were wrong. All ids and challenge_pts_earned need to be of type Int. You can easily spot this, since in a JSON Strings need to be delimited by quotes. When decoding Dates, you also need to specify what date format to use.

startDate and endDate are also dates, so even though they can be decoded as Strings, I'd recommend you store them as actual Date objects.

Please also conform to the Swift naming convention, which is upperCamelCase for types and lowerCamelCase for variable and function names. You can map your custom variable names to their JSON representation using a custom type conforming to 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)
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116