NEW: Learn to build the incredible iOS 15 Weather app today! >>

Day 40: json decode fatal error for json text containing escape characters

Forums > 100 Days of SwiftUI

In the Bundle extension, the fatalError kills the app when trying to decipher the Astronaut.json text. I checked the text coming in and that looks ok. I even made a """ """ string with one astronaut and used the previous lesson's json decoder and it also fails to parse. I thought it might be a bad character but I eventually found it to be the \" and \n literals in the text which are causing the parsing to fail. I tried a clean build and both xcode 13.1 and 13.2 b2 have the same problem. The decoding works on all the examples I tried which don't include these escape characters.

Anyone else run across this?

guard let loaded = try? JSONDecoder().decode([String: Astronaut].self, from: data) else { fatalError("Failed to decode (file) from bundle.") }

1      

In this post from Paul there is a proper error handling for JSON. We only know that the the JSON can't be decoded because the app crashes when decoding is failing.

Replace your guard statement with a proper do catch block and use the code Paul provided in the linked post. If you find out what the problem with the JSON is you can post it here or post the error message so we can help you.

PS: Paul's tutorial code usually works. I don't want to sound to be rude. But with a proper error handling we can spot the problem which could be a specific to your environment.

1      

Thank you for the response and help. The problem went away after I finder copied the "astronauts.json" file and used the copy in the project (bundle). I then tried the original file I had downloaded from Github and it also worked. Although the code is working now, I am not happy about it becuase I don't know why it was not working before.

I apologize if I sounded like the tutorial code was bad. That was not my intent and I assumed this code had already worked for most/all people in the past and I had some strange configuration/environment issue maybe (if I was lucky) one other person had.

I also found Paul's post where he provides the more detailed json decode routine. This also worked fine without finding an error. It is good to have this routine in the toolbox. Again, thank you for taking the time to help.

   

@ROWard, I think your response is exactly why the multi step validation is critical for new programmers to know.

  1. Create a URL
  2. Is the URL valid form?
  3. Look for the file.
  4. If found, is it a valid file?
  5. Get the contents.
  6. Can I decode the contents?

There are so many steps where just reading the data can go wrong!

It's too bad you didn't figure out why you had the initial problem. Perhaps you introduced a stray character in your JSON ? Who knows, you've moved on. But the next time, you'll have better clues.

Please mark @hatsushira's response as SOLVED.

   

Hi @ROWard47

JSON files have a specific format and if you miss a comma or bracket from the file then it will not be able to decode it. I was going to suggest that you use a JSON decoder programme there are some online however I use Ducky Model Editor and would of pasted the JSON in it and it would of tell you where the error in the JSON code was.

I also going to suggest is to downlaod the files again.

   

In this case the video show a shortened version of the code below which @Hatsushira pointed out and if you are going to use it a project then it would be better, but think that you were given the file and knew the it was correct then error should have been in your code!

This code is great and has the option to change the DateDecodingStrategy eg .iso8601 and the KeyDecodingStrategy eg .convertFromSnakeCase at the call site.

extension Bundle {
    func decode<T: Decodable>(
        _ type: T.Type,
        from file: String,
        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
        keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys
    ) -> 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()
        decoder.dateDecodingStrategy = dateDecodingStrategy
        decoder.keyDecodingStrategy = keyDecodingStrategy

        // swiftlint:disable line_length
        do {
            return try decoder.decode(T.self, from: data)
        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' - \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode \(file) from bundle due to type mismatch - \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing \(type) value = \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON.")
        } catch {
            fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
        }
    }
}

If you want to use then you need to change the call to decode the date correctly as it a customize date.

let astronauts = Bundle.main.decode([Astronaut].self, from: "astronauts.json")
let missions: [Mission]

init() {
    let formatter = DateFormatter()
    formatter.dateFormat = "y-MM-dd"
    missions = Bundle.main.decode([Mission].self, from: "missions.json", dateDecodingStrategy: .formatted(formatter))
}

   

@Hatsushira I've got exactly the same error as @ROWard47 has by following the Day 40 lesson. After I replaced all instances of [String: Astronaut] with [Astronaut] the code started to compile again.

However, I'm on the Day 41 lesson, as for some reason the astronauts[member.name] part gives me an error "No exact matches in call to subscript":

self.crew = mission.crew.map {member in
    if let astronaut = astronauts[member.name] {
        return CrewMember(role: member.role, astronaut: astronaut)
    } else {
        fatalError("Missing \(member.name)")
    }
}

Here's a screenshot

I'll try to debug with the do / catch block instead of guard (newbie designer here :) ). But if somebody have any ideas how to resolve this issue, I'd be very grateful. Thank you!

   

@ROWard47 It seems that I was using the wrong version of the astronauts.json file. The file has been probably updated by Paul after I've started following the course.

Here's how my initial version of the file was looking:

[
    {
        "id": "grissom",
        "name": "Virgil I. \"Gus\" Grissom",
        "description": "Virgil Ivan \"Gus\" Grissom ..."
    },
  ...
]

The Xcode yelled at me that I'm trying to compile an array instead of a dictionary. And here's how the updated Paul's JSOn file looks like in the video:

{
    "grissom": {
        "id": "grissom",
        "name": "Virgil I. \"Gus\" Grissom",
        "description": "Virgil Ivan \"Gus\" Grissom ..."
    },
    ...
}

It's a dictionary, not an array as it used to be. After reverting my code back to Day 40 version, commenting out new stuff like CrewMember and updating the astronauts.json file to a new version the code works just fine.

Maybe this will help somebody who will stuck with the same issue :) Cheers!

   

Hacking with Swift is sponsored by Sentry

SPONSORED With Sentry’s error and performance monitoring for iOS, you see mobile vitals that actually matter, can solve any latency issues quickly, and learn how each release is performing over time.

Learn More

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

Reply to this topic…

You need to create an account or log in to reply.

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.