UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Decode JSON error

Forums > SwiftUI

Background

I completed the iDine tutorial here on hackingwithswift.com. This is the source code (this will download the original iDine project to your computer - this link is found on the previous link's page about halfway down). Basically, it is a resturant app, the JSON is the menu. It is a nested JSON menu with 2 layers; contents of the first layer are the MenuSections (ex. breakfast) and the second layer are the MenuItems (ex. pancakes) - MenuSection and MenuItem are the structs inside the app to decode the JSON. I'm trying to improve my understanding of coding JSON with SwiftUI so I am trying to add a third layer to the menu.

My menu is for a pizza place with three sizes of pizza. For my instance, the first layer (MenuSection) would be pizza. The second layer (ItemType) would be cheese pizza. The third layer (MenuItem) would be a large, med, or small cheese pizza.

Problem

When I try to run the app, the build succeeds but the app then crashes because it cannot decode the JSON. The way the JSON gets decoded is inside of a file called Helper.swift that can be seen below or in the source code. I tried using the debugging statements from this HWS Forum post to see what is going on but the way that the JSON is being decoded in Helper.swift somehow prevents me from building the code - I'm unsure why (the error is that no errors occur in the do statement; however there certainly are errors occuring when there is no do - hence my post).

I'm struggling to get more information on what is failing when trying to decode the JSON.

I have already used JSONlint.com to make sure that my JSON is formatted correctly. I've checked that the names of the attributes inside my structs have the same names as the JSON values (no need to use a decoding scheme). I've checked to make sure all JSON values have a corresponding attribute inside of a struct to map to. I have three unique structs so that the three levels of my JSON can be decoded.

Code

Here is my code for reference. First, the JSON (menu.json):

[{
    "id": "EF1CC5BB-4785-4D8E-AB98-5FA4E00B6A66",
    "name": "Pizza",
    "types": [{
            "id": "5BB-4785-4D8E-AB98-5FA4E0",
            "name": "Cheese Pizza",
            "description": "Pizza with cheese",
            "restrictions": ["G", "V"],
            "photoCredit": "Joe Mama",
            "items": [{
                    "id": "EDC3D038C-036F-4C40-826F-61C88CD84DDD",
                    "name": "Large Cheese Pizza",
                    "price": 16.00
                },
                {
                    "id": "EDCD038C-036F-4C40-826F-61C88CD84DDD",
                    "name": "Small Cheese Pizza",
                    "price": 6.00
                }
            ]
        },
        {
            "id": "BB-4785-4D8E-AB98-5FA4E0",
            "name": "Pepperoni Pizza",
            "description": "Pizza with pep",
            "restrictions": ["G", "V"],
            "photoCredit": "Jo",
            "items": [{
                "id": "E038C-036F-4C40-826F-61C88CD84DDD",
                "name": "Large Pepperoni Pizza",
                "price": 6.00
            }]
        }
    ]
}]

Next, here are my structs (Menu.swift):

import SwiftUI

struct MenuSection: Codable, Identifiable { //father
    var id: UUID
    var name: String
    var types: [ItemType]
}

struct ItemType: Codable, Identifiable, Hashable, Equatable { //child of MenuSeciton
    var id: UUID
    var name: String
    let description: String
    let restrictions: [String]
    let photoCredit: String
    var items: [MenuItem]
    var mainImage: String {
        name.replacingOccurrences(of: " ", with: "-").lowercased()
    }

    var thumbnailImage: String {
        "\(mainImage)-thumb"
    }
}

struct MenuItem: Codable, Equatable, Identifiable, Hashable { //child of ItemType
    let id: UUID
    let name: String
    let price: Double
}

Lastly, here is where the JSON initially gets decoded (Helper.swift):

import UIKit

extension Bundle {
    func decode<T: Decodable>(_ type: T.Type, from file: String) -> T {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }

        let decoder = JSONDecoder()

        guard let loaded = try? decoder.decode(T.self, from: data) else { //<<<<<<<<<<<<<this line decodes the JSON
            fatalError("Failed to decode \(file) from bundle.")
        }

        return loaded
    }
}

Note: the error that I recieve when trying to run the app is Fatal error: Failed to decode menu.json from bundle.: file iDine/Helper.swift, line 25 which is helpful and all but doesn't really let me see the issue behind the scenes.

Any help is appreciated. Thanks!

2      

Number one advice when having trouble decoding JSON:

DO NOT use try? when decoding. This masks any error that occurs by turning it into an Optional.

(Once development is done and you know your code works and your JSON is valid, then you can change back to using try? if you want.)

Do something like this instead:

do {
    let loaded = try decoder.decode(T.self, from: data)
    return loaded
} catch {
    print(error)
    fatalError("Failed to decode \(file) from bundle.")
}

If you do, you will see an error like this:

dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "types", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "id", intValue: nil)], debugDescription: "Attempted to decode UUID from invalid UUID string.", underlyingError: nil))

This error is because most of the UUIDs you have in your JSON are not valid UUIDs.

  • 5BB-4785-4D8E-AB98-5FA4E0 is only 21 characters long; UUIDs have to be 32 characters.
  • EDC3D038C-036F-4C40-826F-61C88CD84DDD is 33 characters.
  • BB-4785-4D8E-AB98-5FA4E0 is 20 characters.
  • E038C-036F-4C40-826F-61C88CD84DDD is 29 characters.

(In case it's not obvious, the hyphens - don't count towards the length of the UUID.)

4      

@roosterboy thank you! Incredible. For some reason the do/catch statement that I tried to use from this comment wasn't working but I think it's because I had let loaded = try? decoder.decode(T.self, from: data) instead of let loaded = try decoder.decode(T.self, from: data) which is the Optional you might have been talking about.

And thank you for the info on UUID. As you can tell, I'm a novice. After reading the wiki on UUID, it all makes sense as to why the app was getting hung up on decoding those id's. Thanks again!

2      

@roosterboy thank you! Incredible. For some reason the do/catch statement that I tried to use from this comment wasn't working but I think it's because I had let loaded = try? decoder.decode(T.self, from: data) instead of let loaded = try decoder.decode(T.self, from: data) which is the Optional you might have been talking about.

Yup. In fact, I'm a little surprised Xcode didn't give you a warning that the catch block would never be reached.

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.