SwiftUI新手求助:本地JSON解析至ListView失败
Hey there! Let's break down what's going wrong with your JSON parsing in SwiftUI and fix it step by step. The core issue is a mismatch between your data model and the actual structure of your JSON, plus a few small syntax/logic errors in your code.
Key Problems in Your Current Code
- Incorrect
sentencetype: Your JSON'ssentencefield isn't a string—it's a nested dictionary of key-value pairs (likesentence1: "I eat the apple"). Defining it asStringin your models is why parsing fails. - CodingKeys mismatch: In your second attempt, you mapped
nameto"language"inCodingKeys, but your JSON uses"name"for that field. - ObservableObject usage error: In
ContentView, you tried to useTwelveData(your model) as an@ObservedObjectinstead of yourTwelveSentencesdata loader class. - Unsafe try in DispatchQueue: Your second attempt's
DispatchQueue.main.asyncblock contains atrywithout ado-catchwrapper, which will cause a runtime error.
Fixed Solution
Let's rewrite everything to match your JSON structure properly:
1. Correct Data Model
We'll use a dictionary for sentence to handle the dynamic keys (like sentence1, sentence1a), and add a computed property to sort the sentences logically:
import SwiftUI import Foundation struct LanguageData: Codable, Identifiable { let id: Int let name: String let sentence: [String: String] // Matches the nested JSON object // Computed property to sort sentences by number + suffix (e.g., sentence1 < sentence1a < sentence2) var sortedSentences: [(key: String, value: String)] { sentence.sorted { let key1 = $0.key.replacingOccurrences(of: "sentence", with: "") let key2 = $1.key.replacingOccurrences(of: "sentence", with: "") // Sort by numeric part first, then alphabetical suffix if let num1 = Int(key1.filter(\.isNumber)), let num2 = Int(key2.filter(\.isNumber)) { if num1 != num2 { return num1 < num2 } else { return key1 < key2 } } return key1 < key2 } } }
2. Data Loader Class
Clean up the loading logic with proper error handling and no unnecessary main thread dispatch (JSON parsing doesn't need to run on the main thread, and @Published will handle UI updates automatically):
class LanguageDataLoader: ObservableObject { @Published var languages = [LanguageData]() init() { loadData() } private func loadData() { guard let fileURL = Bundle.main.url(forResource: "twelveData", withExtension: "json") else { print("Error: Could not find twelveData.json in the app bundle") return } do { let data = try Data(contentsOf: fileURL) let decodedLanguages = try JSONDecoder().decode([LanguageData].self, from: data) // Sort languages by ID to match your original intent self.languages = decodedLanguages.sorted { $0.id < $1.id } } catch { print("JSON Parsing Error: \(error.localizedDescription)") print("Full error details: \(error)") } } }
3. Content View
Use the data loader correctly, and display the sentences in a structured, readable way:
struct ContentView: View { @ObservedObject var dataLoader = LanguageDataLoader() var body: some View { // Use NavigationStack for iOS 16+ (replace with NavigationView for older versions) NavigationStack { List(dataLoader.languages) { language in Section(header: Text(language.name).font(.title2).bold()) { ForEach(language.sortedSentences, id: \.key) { sentence in VStack(alignment: .leading) { Text(sentence.key) .font(.caption) .foregroundColor(.secondary) Text(sentence.value) .font(.body) .padding(.top, 2) } } } } .navigationTitle("Language Sentences") .listStyle(.insetGrouped) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Final Notes
- Make sure your
twelveData.jsonfile has its Target Membership checked (select the file in Xcode, then check your app's target in the right sidebar). This ensures it's included in the app bundle. - The
sortedSentencesproperty ensures your sentences display in logical order (not random dictionary order), which is crucial for readability.
内容的提问来源于stack exchange,提问作者Daniel Francis

