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

SOLVED: Decode JSON fails at end of first line...

Forums > Swift

I am loading a JSON from a URL. The loading appears to work, but Decoding fails with this message:

Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around line 1, column 153." UserInfo={NSDebugDescription=Invalid value around line 1, column 153., NSJSONSerializationErrorIndex=153}))

  • Character 153 is at the end of line 1 Here is the start of the JSON: {"RegattaID": "10162","RegattaName": "DF 65 Florida Travelers Trophy Gerd Petersen Memorial","startDate": "3/4/2023","endDate": "3/4/2023","Skippers":[ {"RegattaMemberRegID": "10690","SailNo": "222","HullNo": "222","Firstname": "George","LastName": "Balaschak","Email": "georgeb@tlccar.com","Phone": "","Cell": "561 844-5411"},

I am running out of ideas and a little help would be welcome. Thanks...

Here is my code:

import SwiftUI
import Foundation

struct AMYARegatta: Codable, Identifiable {
    var id: String { RegattaID }

    var RegattaID: String = "0000"
    var RegattaName: String = "xxxx"
    var startDate: Date = Date.now 
    var endDate: Date = Date.now 
    var Skippers: [AMYAskip] = []
}
struct AMYAskip: Codable {
    let RegattaMemberRegID: String
    let SailNo: String
    let HullNo: String
    let Firstname: String
    let LastName: String
    let Email: String
    let Phone: String
    let Cell: String
}

struct AddAMYARegatta: View {

    @EnvironmentObject var regattaList : RegattaList

    @Environment(\.dismiss) var dismiss

    @State private var amyaRegatta = AMYARegatta()

        var body: some View {
        Text("Regatta: \(amyaRegatta.RegattaName)")

            .task {
                await loadAMYA(AMYARegattaID: "10162")
            }

    }
    func loadAMYA(AMYARegattaID: String) async {
        guard let url = URL(string: "https://www.theamya.org/RegattaRegReportJson.asp?RgID=\(AMYARegattaID)") else {
            print("Invalid URL")
            return
        }
        print(url)

        let decoder = JSONDecoder()
        let formatter = DateFormatter()
        formatter.dateFormat = "MM-dd-yyyy"
        decoder.dateDecodingStrategy = .formatted(formatter)

        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            print("data = \(data)")

            do {
                let loaded = try decoder.decode(AMYARegatta.self, from: data)
                print(loaded as Any)
                print("decoded = \(loaded)")
            } 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)
                fatalError("Failed to decode ")
            }

        } catch {
            print("Invalid data")
        }

        var AMYASkippers: [Skipper] = []
        for Askip in amyaRegatta.Skippers {
            let newSkip = Skipper()
            newSkip.RegattaMemberRegID = Askip.RegattaMemberRegID
            newSkip.id = UUID()
            newSkip.firstName = Askip.Firstname
            newSkip.lastName = Askip.LastName
            newSkip.sailNum = Int(Askip.SailNo) ?? 0
            newSkip.boatNum = Int(Askip.HullNo) ?? 0
            newSkip.Phone = Askip.Phone
            newSkip.Email = Askip.Email
            newSkip.Cell = Askip.Cell
            AMYASkippers.append(newSkip)
        }

        let newRegatta = Regatta()
        newRegatta.id = UUID()
        newRegatta.name = amyaRegatta.RegattaName
        newRegatta.startDate = amyaRegatta.startDate
        newRegatta.endDate = amyaRegatta.endDate
        newRegatta.skippers = AMYASkippers
        regattaList.add(newRegatta)
        regattaList.save()
    }
}

2      

It's because your JSON contains invalid characters. In this particular case, it contains a linebreak character. And not just a linebreak, an HTML linebreak tag! You can see it when I dump the raw JSON returned from your API call:

invalid JSON character

You will need to clean up your JSON before you can decode it because there are several of these.

Looks like there's also </body> and </html> tags in there as well. Those will have to go too.

I have no idea why this API would be including a <br> in their JSON, but there you go. The API seems to be serving up this data with the Content-Type header set to text/html instead of text/json, so unless you can get them to fix it, you will have to compensate on your end.

Put something like this in after you get the data but before you try decoding it:

let cleanJSONData = String(decoding: data, as: UTF8.self)
    .replacingOccurrences(of: "<br>", with: "")
    .replacingOccurrences(of: "</body>", with: "")
    .replacingOccurrences(of: "</html>", with: "")
    .data(using: .utf8)!

And make sure you use cleanJSONData when decoding instead of data.

4      

@rooster! nice find!

What tool did you use to dump the raw JSON?

I used https://jsonformatter.org/ and it reported this was valid JSON.

You picked a better tool!

2      

I just dumped the raw data in a playground using this:

print(String(decoding: data, as: UTF8.self))

But I also checked it out in RapidAPI (formerly known as Paw), which is how I found the info about the Content-Type header.

I used https://jsonformatter.org/ and it reported this was valid JSON.

Because, I assume, you plugged the API URL into a browser and then copied the resulting JSON out of the browser window and into the JSONFormatter text box. The browser wouldn't show you the <br> and other tags but would convert them to display text, which means you would miss them when you copy/paste the JSON.

That's also why @GerryCSwift's pasting in of the first 300 characters or so of the JSON was kind of useless for diagnosing the problem; the round trip through the browser erased all evidence of the invalid characters.

3      

Thanks @roosterboy ! I had copied and pasted the json from my browser into various validating sites and created a file which decoded fine! Of course, my browser had already suppressed all that html junk ;-)

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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.