JSON Decoder not working with Restaurant Guided App

Im currently working throught the App Development with Swift Xcode 10 book from Apple and I have come to the last guided project 'Restaurant App'. Hopefully there are those of you already familiar with the guided app project and can help, and for those not familiar most all the reference code is found in the book chapter 5.6. My guess is I'm not the first to have this problem, but can't find the exact solution. Up to this point I've been able to complete all the lab's and projects to working order and not needed any help from the forums. Everything worked perfectly on this app till it came time to "Submit" the order and call the func submitOrder from the MenuController.swift in where the func will encode the data then set the prepartationTime.prepTime in completion. This issue arises here, I have followed the code line by line and verified there is indeed data. But when trying to unwrap the data and decode it, it returns nil data.


Currently I'm using Xcode 11.5 and Swift 5.2.


Print view output when pressing submit button.

- Call from Order Table to submit the order

OrderTableViewController uploadOrder() menuIDs = [4]


- print data from the submitOrder()

data = ["menuIds": [4]]

jsonData = 15 bytes

request.httpbody = 15 bytes

Submit Order Failed


    func submitOrder(forMenuIDs menuIds: [Int], completion: @escaping (Int?) -> Void) {
        let orderURL = baseURL.appendingPathComponent("order")
        
        var request = URLRequest(url: orderURL)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let data: [String: [Int]] = ["menuIds": menuIds]
        let jsonEncoder = JSONEncoder()
        let jsonData = try? jsonEncoder.encode(data)
        request.httpBody = jsonData

        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            let jsonDecoder = JSONDecoder()
            if let data = data,
                let preparationTime = try? jsonDecoder.decode(PreparationTime.self, from: data)  {
                print(preparationTime)
                completion(preparationTime.prepTime)
            }else {
                print("Submit Order Failed")
                completion(nil)
            }
        }
        task.resume()
    }
Answered by OOPer in 424626022

What is printed if you put `print(String(data: data!, encoding: .utf8))` before the line 15 ?

And plese show the definition of `PreparationTime`.

Accepted Answer

What is printed if you put `print(String(data: data!, encoding: .utf8))` before the line 15 ?

And plese show the definition of `PreparationTime`.

Have you checked value of orderURL


Not sure that would make a difference, but why have you @escaping

OOPer, thank for your response. Here is the results of the print and the struct for PreparationTime

So comparing this time with the menu data provided by the localhost server, the time of 12 is correct, telling me it is in fact accessing the property as it should.

{"preparation_time": 12}


struct Categories: Codable {
    let categories: [String]
}

struct PreparationTime: Codable {
    let prepTime: Int
    
    enum CodingKeys: String, CodingKey {
        case prepTime = "preparation_time"
    }
}


UPDATE: As I was typing this response and noticing by the print string you gave me showed preparation_time of 12 it gave me some insight to dig a little more. I then I spotted the mistake! Such a small stupid mistake that I can't belive I missed during debugging, as I went line by line in my code to find it.....

In my CodingKeys I missed spelled "preparation", I had it as preperation! What a rookie mistake....

Now all is working as expected.


Thanks for the help.

Hey Claude31, although it is working now, just thought I would respond to you. The orderURL was my first checked during debugging and it was providing the correct appending URL.


To answer your second question, it was my understanding that I needed to use the @escaping keyword in the completion if I wanted the closure code to run complete when the function has finished.

Happy to hear you have solved your issue and my post might have been a little help for it.


By the way, everyone might make such a subtle mistake sometimes, so I would not use `try?` which disposes some error info important for debugging.


I would write the closure for dataTask like this:

        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            let jsonDecoder = JSONDecoder()
            if let error = error {
                print(error)
                completion(nil)
                return
            }
            guard let data = data else {
                print("data was nil")
                completion(nil)
                return
            }
            do {
                let preparationTime = try jsonDecoder.decode(PreparationTime.self, from: data)
                completion(preparationTime.prepTime)
            } catch {
                print(error)
                completion(nil)
            }
        }
        task.resume()

A little bit longer than yours, but it shows all the error info when generated, which would be helpful for debugging.

Thanks OOper for the suggestion, I will keep that in mind for the next one!

I'm banging my head against the same issue years later!

Preparation is spelt correctly in my coding keys but when I print the value of 'data' I'm getting:

Optional("{"preparation_time":20}")

I don't know if the additional symbols and punctuation are an issue or not.

JSON Decoder not working with Restaurant Guided App
 
 
Q