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

How do I decode different styles of json files?

Forums > SwiftUI

I am very new and have been trying to adapt some of the json decoding tutorials to a custom json file and I cannot seem to find the right combination of declared structs and decoding. The code below triggers fatalError and a failure to decode from file message.

2      


{
  "user":{
    "trigger1": {
      "title":"Chocolate",
      "id":"e1ee6ffe-6da3-4a25-99d7-285abda8c3ed"
    },
    "trigger2": {
      "title":"Coffee",
      "id":"e1ee6ffe-6da3-4a25-99d7-285abda8c3ed"
    },
    "trigger3": {
      "title":"Red Wine",
      "id":"e1ee6ffe-6da3-4a25-99d7-285abda8c3ed"
    }
  }
}
struct UserTriggers: Codable, Identifiable {
    let title: String
    let id: String
}
extension Bundle {
    func decode(_ file: String) -> [UserTriggers] {
        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([UserTriggers].self, from: data) else {
            fatalError("failed to decode \(file) from bundle")
        }

        return loaded
    }
}
struct ContentView: View {

    let userTrigger = Bundle.main.decode("user1_triggers.json")

    var body: some View {

        Text("\(userTrigger.count)")

    }
}

2      

Hi KenzeeeD Your struct is incorrect for your JSON file

struct UserTriggers: Decodable {
    let user: User

    struct User: Decodable {
        struct Trigger1: Decodable {
            let title: String
            let id: String
        }

        struct Trigger2: Decodable {
            let title: String
            let id: String
        }

        struct Trigger3: Decodable {
            let title: String
            let id: String
        }

        let trigger1: Trigger1
        let trigger2: Trigger2
        let trigger3: Trigger3
    }
}

the extension is now

extension Bundle {
    func decode(_ file: String) -> UserTriggers { // not an array
        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()

        // call not an array
        guard let loaded = try? decoder.decode(UserTriggers.self, from: data) else {
            fatalError("failed to decode \(file) from bundle")
        }

        return loaded
    }
}

and ContentView

struct ContentView: View {
    let userTrigger = Bundle.main.decode("user_triggers.json")

    var body: some View {
        Text("\(userTrigger.user.trigger1.title)")
            .padding()
    }
}

2      

There is a good model editor to construct the struct called Ducky. Stewart Lynch does a good video on how to use Ducky Model Editor for Swift Developers.

For the Bundle Decoder I tend to use Paul's extension which is generic and you define the type at call site. This is the extension

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)")
        }
    }
}

and then in View to call

let userTrigger = Bundle.main.decode(UserTriggers.self, from: "user_triggers.json")

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

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.