ios JSON到SwiftData的JSON解码在ObservationRegistrar错误中结束

pinkon5k  于 11个月前  发布在  iOS
关注(0)|答案(2)|浏览(73)

我正在努力使用iOS 17和SwiftData的SwiftUI应用程序。我试图从JSON源下载数据并将数据存储在SwiftData中。我可以将数据下载并解码到Swift结构中,但无法使用SwiftData @Model类。下面的代码包括结构和SwiftData过程。SwiftData类是SDTopLevel和SDFuelStation,结构体为TopLevelX和FuelStationX。FuelStationListView中的按钮调用loadDataSD或loadDataX。URL正确且包含演示密钥。

struct FuelStationListView: View {

    @Environment(\.modelContext) var context
    @Query(sort: \SDFuelStation.stationName) var fuelStations: [SDFuelStation]

    @State private var sdTopLevel: SDTopLevel?
    @State private var topLevel: TopLevelX?

    var body: some View {
        NavigationStack {
            Button("Fetch") {
                Task {
                    await loadDataX()//works
                    //await loadDataSD()//does not work
                }
            }
            List {
                ForEach(fuelStations) { fuelStation in
                    Text(fuelStation.stationName)
                }
            }
            .navigationTitle("Fuel Stations")
        }//nav
    }//body

    func loadDataSD() async {

        guard let url = URL(string: "https://developer.nrel.gov/api/alt-fuel-stations/v1.json?api_key=DEMO_KEY&limit=10") else {
            print("Invalid URL")
            return
        }

        do {
            let (data, response) = try await URLSession.shared.data(from: url)
            guard (response as? HTTPURLResponse)?.statusCode == 200 else {
                print(response)
                return
            }
        
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            let decodedResponse = try decoder.decode(SDTopLevel.self, from: data)
            print(decodedResponse)
        
            sdTopLevel = decodedResponse
            print("sdTopLevel.fuelStations.count is \(sdTopLevel?.fuelStations.count ?? 1000)")

        } catch {
            print("Invalid Data")
        }

    }//load data

    func loadDataX() async {

        guard let url = URL(string: "https://developer.nrel.gov/api/alt-fuel-stations/v1.json?api_key=DEMO_KEY&limit=10") else {
            print("Invalid URL")
            return
        }

        do {
        
            let (data, response) = try await URLSession.shared.data(from: url)
            guard (response as? HTTPURLResponse)?.statusCode == 200 else {
                print(response)
                return
            }
        
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            let decodedResponse = try decoder.decode(TopLevelX.self, from: data)
        
            self.topLevel = decodedResponse
            print("topLevel.fuelStations.count is \(topLevel?.fuelStations.count ?? 0)")

            for station in decodedResponse.fuelStations {
                print(station.stationName)
            }
        
            self.topLevel = nil

        } catch {
            print("Invalid Data")
        }

    }//load data

}//struct fuel Station list view

字符串
数据显示:

@Model
class SDFuelStation: Codable {

    enum CodingKeys: CodingKey {
        case id, city, stationName, streetAddress
    }//enum

    public var id: Int

    var stationName: String = ""
    var streetAddress: String = ""
    var city: String = ""

    public init(stationName: String, streetAddress: String, city: String) {
        self.id = 0
        self.stationName = stationName
        self.streetAddress = streetAddress
        self.city = city
    }

    required public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        stationName = try container.decode(String.self, forKey: .stationName)
        streetAddress = try container.decode(String.self, forKey: .streetAddress)
        city = try container.decode(String.self, forKey: .city)
    }//required init

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
    
        try container.encode(id, forKey: .id)
        try container.encode(stationName, forKey: .stationName)
        try container.encode(streetAddress, forKey: .streetAddress)
        try container.encode(city, forKey: .city)
    }

}//class

struct TopLevelX: Codable {
    let fuelStations: [FuelStationX]
}

struct FuelStationX: Codable {

    let id: Int

    var stationName: String = ""
    var streetAddress: String = ""
    var city: String = ""

}//struct


错误发生在getter中的Model代码中:
x1c 0d1x的数据
任何指导将不胜感激。Xcode 15.0
编辑:我错误地错过了SDTopLevel类:

@Model
class SDTopLevel: Codable {

    enum CodingKeys: CodingKey {
        case fuelStations
    }

    var fuelStations: [SDFuelStation]

    init(fuelStations: [SDFuelStation]) {
        self.fuelStations = fuelStations
    }

    required public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        fuelStations = try container.decode([SDFuelStation].self, forKey: .fuelStations)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(fuelStations, forKey: .fuelStations)
    }

}


var fuelStations的代码如下展开,错误在行上:return self.getValue(forKey:.fuelStations),错误是“Thread 10:EXC_BREAKPOINT(code=1,subcode= 0x 1a 8a 5303 c)”

{
    @storageRestrictions(accesses: _$backingData, initializes: _fuelStations)
        init(initialValue) {
                    _$backingData.setValue(forKey: \.fuelStations, to: initialValue)
                    _fuelStations = _SwiftDataNoType()
        }

    get {
                    _$observationRegistrar.access(self, keyPath: \.fuelStations)
                    return self.getValue(forKey: \.fuelStations)
        }

    set {
                    _$observationRegistrar.withMutation(of: self, keyPath: \.fuelStations) {
                            self.setValue(forKey: \.fuelStations, to: newValue)
                    }
        }
}

jhkqcmku

jhkqcmku1#

解决方案与我们在Core Data中所做的非常相似,我们需要将模型上下文传递给解码器,以便我们可以在init(from:)中使用它并插入顶级对象。
为此,我们在JSONDecoder上使用userInfo字典,因此首先需要定义一个键

let modelContextKey = CodingUserInfoKey(rawValue: "modelcontext")!

字符串
然后在解码之前,我们将ModelContextEnvironment变量传递给解码器

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.userInfo[modelContextKey] = context


然后,我们读取这个键,并在init(from:)中使用它作为json的根对象

@Model
class SDTopLevel: Codable {
    //...

    required public init(from decoder: Decoder) throws {
        guard let context = decoder.userInfo[CodingUserInfoKey(rawValue: "modelcontext")!] as? ModelContext else {
            fatalError() // replace with throw some error
        }
        let container = try decoder.container(keyedBy: CodingKeys.self)
        fuelStations = try container.decode([SDFuelStation].self, forKey: .fuelStations)
        context.insert(self)
    }


我们不需要对SDFuelStation做同样的事情,因为如果关系的一端已经插入到模型上下文中,那么对于SwiftData就足够了。

xxb16uws

xxb16uws2#

尝试这种方法,在其中声明SDTopLevelSDFuelStation之间的关系。
将json数据直接解码为model类型效果非常好,如本示例代码所示。

import SwiftUI
import SwiftData

@main
struct TestApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([SDTopLevel.self])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(sharedModelContainer)
    }
}

struct ContentView: View {
    @Environment(\.modelContext) var context
    
    var body: some View {
        FuelStationListView()
    }
}

struct FuelStationListView: View {
    @Environment(\.modelContext) var context
    @Query(sort: \SDFuelStation.stationName) var fuelStations: [SDFuelStation]
 //   @Query() var sdTopLevel: [SDTopLevel]

    var body: some View {
        NavigationStack {
            Button("Fetch") {
                Task {
                    await loadDataSD()
                }
            }
//            List {
//                ForEach(sdTopLevel) { sd in
//                    ForEach(sd.fuelStations ?? []) { fuelStation in
//                        Text(fuelStation.stationName)
//                    }
//                }
//            }
            
            List {
                ForEach(fuelStations) { fuelStation in
                    Text(fuelStation.stationName)
                }
            }
            
            .navigationTitle("Fuel Stations")
        }//nav
    }//body
    
    func loadDataSD() async {
        guard let url = URL(string: "https://developer.nrel.gov/api/alt-fuel-stations/v1.json?api_key=DEMO_KEY&limit=10") else {
            print("Invalid URL")
            return
        }
        do {
            let (data, response) = try await URLSession.shared.data(from: url)
            guard (response as? HTTPURLResponse)?.statusCode == 200 else {
                print(response)
                return
            }
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            let decodedResponse = try decoder.decode(SDTopLevel.self, from: data)
            context.insert(decodedResponse)  // <--- here
        } catch {
            print("----> error: \(error)")
        }
    }
    
}

@Model
class SDFuelStation: Identifiable, Codable {  // <--- here
    var id: Int
    var stationName: String = ""
    var streetAddress: String = ""
    var city: String = ""
    
    enum CodingKeys: CodingKey {
        case id, city, stationName, streetAddress
    }
    
    @Relationship var sdTopLevel: SDTopLevel?  // <--- here
    
    required public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        stationName = try container.decode(String.self, forKey: .stationName)
        streetAddress = try container.decode(String.self, forKey: .streetAddress)
        city = try container.decode(String.self, forKey: .city)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(stationName, forKey: .stationName)
        try container.encode(streetAddress, forKey: .streetAddress)
        try container.encode(city, forKey: .city)
    }
 
}

@Model
class SDTopLevel: Identifiable, Codable {  // <--- here
    let id = UUID()
    
    @Relationship(inverse: \SDFuelStation.sdTopLevel) 
    var fuelStations: [SDFuelStation]?  // <--- here
    
    enum CodingKeys: CodingKey {
        case fuelStations
    }

    required public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        fuelStations = try container.decode([SDFuelStation].self, forKey: .fuelStations)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(fuelStations, forKey: .fuelStations)
    }
    
}

字符串

相关问题