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

SOLVED: Why can't I parse this JSON data? Error: "Couldn't parse *.json as Array<Competition>"

Forums > SwiftUI

I am a complete SwiftUI beginner. I have gone through Apple's Landmarks tutorial, and a bunch of the videos here. I am now trying to render out a view with some alternative JSON as the data.

However, when I try and use that data I get a Fatal Error in ModelData.swift - crashed due to fatalError in ModelData.swift at line 40

The logs don't show me anything I understand, they don't even reference that same message so I am lost.

I thought it must be something to do with how I have mapped the data. Here is my mapping of the JSON called Competition.swift, followed by the JSON, followed by my simple view.

Hope somebody can shed some light on what I have wrong here?

import Foundation
import SwiftUI

struct Competition: Codable, Identifiable {
    var id: UUID
    var _id: String
    var title: String
    var titleShort: String
    var classification: Classification
    var fixtures: [FixtureSingle]
    struct Classification: Codable {
        var title: String
        var bId: Int
    }
    struct FixtureSingle: Codable {
        var _id: String
        var title: String
        var date: String
        var venue: String
        var classification: Classification
        var competition: Competition
        var referee: Referee
        var participants: [Participant]
        var previousMeetings: PreviousMeeting
    }
    struct Referee: Codable {
        var titleShort: String
        var nationality: String
        var title: String
    }
    struct Participant: Codable {
        var titleShort: String
        var title: String
        var _id: String
        var kits: [Kit]
        var logo: String
        var nationality: String
        var preMatchStats: PreMatchStat
        var classification: Classification
        var form: [Form]
    }
    struct Kit: Codable {
        var asset: String
        var mainColour: String
    }
    struct PreMatchStat: Codable {
        var leadingScorer: LeadingScorer
        var leaguePosition: Int
        var cleanSheets: Double
        var atLeastOneGoal: Double
        var goalsScoredAverage: Double
        var goalsConcededAverage: Double
        var goalDifference: Int
        var winPercentage: Double
    }
    struct LeadingScorer: Codable {
        var totalGoals: Int
        var title: String
        var titleShort: String
    }
    struct Form: Codable {
        var date: String
        var titleShort: String
        var scoreline: [String]
        var winner: Int
        var participants: [FormParticipant]
    }
    struct FormParticipant: Codable {
        var _id: String
    }
    struct PreviousMeeting: Codable {
        var titleShort: String
        var winner: Int
        var date: Date
        var scoreline: [String]
        var competition: Competition
        var participants: [Participant]
    }

}

And here is the JSON file:

[{
"_id": "6304f394d05afb456f03d654",
      "title": "England Premier League",
      "titleShort": "EPL",
      "classification": {
        "title": "Soccer",
        "bId": 1
      },
      "fixtures": [
        {
          "_id": "6304f394d05afb456f03d656",
          "title": "Tottenham v Wolverhampton",
          "date": "Sat Aug 20 2022 12:30:00 GMT+0100 (British Summer Time)",
          "venue": "Tottenham Hotspur Stadium",
          "classification": {
            "bId": 1,
            "title": "Soccer"
          },
          "competition": {
            "_id": "6304f394d05afb456f03d654",
            "title": "England Premier League"
          },
          "referee": {
            "titleShort": "S Hooper",
            "nationality": "England",
            "title": "Simon Hooper"
          },
          "participants": [
            {
              "titleShort": "TOT",
              "title": "Tottenham",
              "_id": "6304f392d05afb456f03285d",
              "kits": [
                {
                  "asset": "Tottenham_Hotspur_Home_23",
                  "mainColour": "#ffffff"
                }
              ],
              "preMatchStats": {
                "leadingScorer": {
                  "totalGoals": 4,
                  "title": "Harry Kane",
                  "titleShort": "H Kane"
                },
                "leaguePosition": 4,
                "cleanSheets": 0.71,
                "atLeastOneGoal": 0.75,
                "goalsScoredAverage": 2.29,
                "goalsConcededAverage": 1.11,
                "goalDifference": 33,
                "winPercentage": 14.29
              },
              "classification": {
                "bId": 1
              },
              "form": [
                {
                  "date": "Sun Aug 14 2022 16:30:00 GMT+0100 (British Summer Time)",
                  "titleShort": "CHE v TOT",
                  "scoreline": [
                    "2-2"
                  ],
                  "winner": -1,
                  "participants": [
                    {
                      "_id": "6304f392d05afb456f032a72"
                    },
                    {
                      "_id": "6304f392d05afb456f03285d"
                    }
                  ]
                }

              ]
            },
            {
              "titleShort": "WOL",
              "title": "Wolverhampton",
              "_id": "6304f392d05afb456f032886",
              "kits": [
                {
                  "asset": "Wolverhampton_Wanderers_Home_23",
                  "mainColour": "#fecd32"
                }
              ],
              "preMatchStats": {
                "leadingScorer": {
                  "totalGoals": 3,
                  "title": "Daniel Podence",
                  "titleShort": "D Podence"
                },
                "leaguePosition": 14,
                "cleanSheets": 0.72,
                "atLeastOneGoal": 0.72,
                "goalsScoredAverage": 1.34,
                "goalsConcededAverage": 1.24,
                "goalDifference": 3,
                "winPercentage": 17.24
              },
              "classification": {
                "bId": 1
              },
              "form": [
                {
                  "date": "Sat Aug 13 2022 15:00:00 GMT+0100 (British Summer Time)",
                  "titleShort": "WOL v FUL",
                  "scoreline": [
                    "0-0"
                  ],
                  "winner": -1,
                  "participants": [
                    {
                      "_id": "6304f392d05afb456f032886"
                    },
                    {
                      "_id": "6304f392d05afb456f032953"
                    }
                  ]
                },
              ]
            }
          ],
          "previousMeetings": [
            {
              "titleShort": "TOT v WOL",
              "winner": 1,
              "date": "Sun Feb 13 2022 14:00:00 GMT+0000 (Greenwich Mean Time)",
              "scoreline": [
                "0-2"
              ],
              "competition": {
                "titleShort": "EPL"
              },
              "participants": [
                {
                  "titleShort": "TOT",
                  "_id": "6304f392d05afb456f03285d"
                },
                {
                  "titleShort": "WOL",
                  "_id": "6304f392d05afb456f032886"
                }
              ]
            },
            {
              "titleShort": "WOL v TOT",
              "winner": 1,
              "date": "Wed Sep 22 2021 19:45:00 GMT+0100 (British Summer Time)",
              "scoreline": [
                "2-2"
              ],
              "competition": {
                "titleShort": "EFL"
              },
              "participants": [
                {
                  "titleShort": "WOL",
                  "_id": "6304f392d05afb456f032886"
                },
                {
                  "titleShort": "TOT",
                  "_id": "6304f392d05afb456f03285d"
                }
              ]
            },
            {
              "titleShort": "WOL v TOT",
              "winner": 1,
              "date": "Sun Aug 22 2021 14:00:00 GMT+0100 (British Summer Time)",
              "scoreline": [
                "0-1"
              ],
              "competition": {
                "titleShort": "EPL"
              },
              "participants": [
                {
                  "titleShort": "WOL",
                  "_id": "6304f392d05afb456f032886"
                },
                {
                  "titleShort": "TOT",
                  "_id": "6304f392d05afb456f03285d"
                }
              ]
            },
            {
              "titleShort": "TOT v WOL",
              "winner": 0,
              "date": "Sun May 16 2021 14:05:00 GMT+0100 (British Summer Time)",
              "scoreline": [
                "2-0"
              ],
              "competition": {
                "titleShort": "EPL"
              },
              "participants": [
                {
                  "titleShort": "TOT",
                  "_id": "6304f392d05afb456f03285d"
                },
                {
                  "titleShort": "WOL",
                  "_id": "6304f392d05afb456f032886"
                }
              ]
            },
            {
              "titleShort": "WOL v TOT",
              "winner": -1,
              "date": "Sun Dec 27 2020 19:15:00 GMT+0000 (Greenwich Mean Time)",
              "scoreline": [
                "1-1"
              ],
              "competition": {
                "titleShort": "EPL"
              },
              "participants": [
                {
                  "titleShort": "WOL",
                  "_id": "6304f392d05afb456f032886"
                },
                {
                  "titleShort": "TOT",
                  "_id": "6304f392d05afb456f03285d"
                }
              ]
            }
          ]
        }
      ]
}]

And here is the view that won't load/preview:

import SwiftUI

struct FixtureList: View {
    @Environment(ModelData.self) var modelData
    var body: some View {
        Text("hello")
    }
}

#Preview {
    FixtureList().environment(ModelData())
}

2      

Don't throw a FatalError when decoding but catch the error.

Here you find an example how to catch the error when decoding a json. This gives you a hint where to look.

2      

It maybe because you added

var id: UUID

Try changing to this

var id: UUID { UUID() }

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!

@NigelGee sadly that made no difference. Will likely just strip my model and data right back and try adding them in one by one.

@Hatsushira - I've added a print(error) in above my catch but it my model data, is there something else I should be be doing?

Here is the modelData:

import Foundation

@Observable
class ModelData {
    var competition: [Competition] = load("fixtures-pre-soccer-premier-league.json")

    var categories: [String: [Competition]] {
        Dictionary(
            grouping: competition,
            by: {$0.title}
        )
    }
}

func load<T: Decodable>(_ filename: String) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        print(error)
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

2      

You want to know exactly as possible where the error is:

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)' not found – \(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)")
    }

2      

Hi @benfrain I added your json files to my project and add this decoder

extension Bundle {
    func decode<T: Decodable>(
        _ type: T.Type = T.self,
        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)")
        }
    }
}

Then added this model

struct Competition: Codable, Identifiable {
    var id: UUID { UUID() }
    var _id: String
    var title: String
    var titleShort: String
    var classification: Classification
    var fixtures: [Fixture]

    struct Classification: Codable {
        var title: String
        var bId: Int
    }

    struct Fixture: Codable {
        struct Classification: Codable {
            var bId: Int
            var title: String
        }

        struct Competition: Codable {
            var _id: String
            var title: String
        }

        struct Referee: Codable {
            var titleShort: String
            var nationality: String
            var title: String
        }

        struct Participant: Codable {
            struct Kit: Codable {
                var asset: String
                var mainColour: String
            }

            struct PreMatchStat: Codable {
                struct LeadingScorer: Codable {
                    var totalGoals: Int
                    var title: String
                    var titleShort: String
                }

                var leadingScorer: LeadingScorer
                var leaguePosition: Int
                var cleanSheets: Double
                var atLeastOneGoal: Double
                var goalsScoredAverage: Double
                var goalsConcededAverage: Double
                var goalDifference: Int
                var winPercentage: Double
            }

            struct Classification: Codable {
                var bId: Int
            }

            struct Form: Codable {
                struct Participant: Codable {
                    var _id: String
                }

                var date: String
                var titleShort: String
                var scoreline: [String]
                var winner: Int
                var participants: [Participant]
            }

            var titleShort: String
            var title: String
            var _id: String
            var kits: [Kit]
            var preMatchStats: PreMatchStat
            var classification: Classification
            var form: [Form]
        }

        struct PreviousMeeting: Codable {
            struct Competition: Codable {
                var titleShort: String
            }

            struct Participant: Codable {
                var titleShort: String
                var _id: String
            }

            var titleShort: String
            var winner: Int
            var date: String
            var scoreline: [String]
            var competition: Competition
            var participants: [Participant]
        }

        var _id: String
        var title: String
        var date: String
        var venue: String
        var classification: Classification
        var competition: Competition
        var referee: Referee
        var participants: [Participant]
        var previousMeetings: [PreviousMeeting]
    }
}

Then created this in ContentView

struct ContentView: View {
    let competitions = Bundle.main.decode([Competition].self, from: "Competition.json")

    var body: some View {
        List(competitions) {
            Text($0.title)
        }
    }
}

Showed in preview

English Premier League

So worked fine You may have trouble with _id as even though snake case will not decode as such (unless you use CodingKey) So the model would look like this and will work without `id: UUID { UUID() }

struct Competition: Codable, Identifiable {
    var id: String
    var title: String
    var titleShort: String
    var classification: Classification
    var fixtures: [Fixture]

    private enum CodingKeys: String, CodingKey {
        case id = "_id"
        case title
        case titleShort
        case classification
        case fixtures
    }

    struct Classification: Codable {
        var title: String
        var bID: Int

        private enum CodingKeys: String, CodingKey {
            case title
            case bID = "bId"
        }
    }

    struct Fixture: Codable {
        struct Classification: Codable {
            var bID: Int
            var title: String

            private enum CodingKeys: String, CodingKey {
                case bID = "bId"
                case title
            }
        }

        struct Competition: Codable {
            var id: String
            var title: String

            private enum CodingKeys: String, CodingKey {
                case id = "_id"
                case title
            }
        }

        struct Referee: Codable {
            var titleShort: String
            var nationality: String
            var title: String
        }

        struct Participant: Codable {
            struct Kit: Codable {
                var asset: String
                var mainColour: String
            }

            struct PreMatchStat: Codable {
                struct LeadingScorer: Codable {
                    var totalGoals: Int
                    var title: String
                    var titleShort: String
                }

                var leadingScorer: LeadingScorer
                var leaguePosition: Int
                var cleanSheets: Double
                var atLeastOneGoal: Double
                var goalsScoredAverage: Double
                var goalsConcededAverage: Double
                var goalDifference: Int
                var winPercentage: Double
            }

            struct Classification: Codable {
                var bID: Int

                private enum CodingKeys: String, CodingKey {
                    case bID = "bId"
                }
            }

            struct Form: Codable {
                struct Participant: Codable {
                    var id: String

                    private enum CodingKeys: String, CodingKey {
                        case id = "_id"
                    }
                }

                var date: String
                var titleShort: String
                var scoreline: [String]
                var winner: Int
                var participants: [Participant]
            }

            var titleShort: String
            var title: String
            var id: String
            var kits: [Kit]
            var preMatchStats: PreMatchStat
            var classification: Classification
            var form: [Form]

            private enum CodingKeys: String, CodingKey {
                case titleShort
                case title
                case id = "_id"
                case kits
                case preMatchStats
                case classification
                case form
            }
        }

        struct PreviousMeeting: Codable {
            struct Competition: Codable {
                var titleShort: String
            }

            struct Participant: Codable,  Identifiable{
                var titleShort: String
                var id: String

                private enum CodingKeys: String, CodingKey {
                    case titleShort
                    case id = "_id"
                }
            }

            var titleShort: String
            var winner: Int
            var date: String
            var scoreline: [String]
            var competition: Competition
            var participants: [Participant]
        }

        var id: String
        var title: String
        var date: String
        var venue: String
        var classification: Classification
        var competition: Competition
        var referee: Referee
        var participants: [Participant]
        var previousMeetings: [PreviousMeeting]

        private enum CodingKeys: String, CodingKey {
            case id = "_id"
            case title
            case date
            case venue
            case classification
            case competition
            case referee
            case participants
            case previousMeetings
        }
    }
}

So now can do this

struct ContentView: View {
    let competitions = Bundle.main.decode([Competition].self, from: "Competition.json")

    var body: some View {
        List(competitions) { competition in
            Text(competition.title)
            Text(competition.classification.title)

            ForEach(competition.fixtures) { fixture in
                Text(fixture.title)
            }
        }
    }
}

2      

@NigelGee thanks very much for looking at that. I was absolutely clueless at this point!

Looks like one of the main things I got wrong is not following the nesting structure of the JSON in the structs. I somehow got it in my head that the nested structs could be defined flatter.

2      

If you use some thing like Ducky you can choose your model (sometime might have to amend but it get you most of the way). Stewart Lynch does a YouTube Video Ducky Model Editor for Swift Developers

2      

Sorry for the basic questions @NigelGee; Using your second model, and the second version of body, I get an error .../ContentView.swift:18:17 Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'Competition.Fixture' conform to 'Identifiable' what is Swift looking for here?

2      

Add Identifiable to struct Fixture: Codable {

struct Fixture: Codable, Identifiable {

3      

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!

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.