将Firestore中的对象数组映射到swift中的字典数组

将Firestore中的对象数组映射到swift中的字典数组,swift,google-cloud-firestore,Swift,Google Cloud Firestore,我的数据结构如下所示,其中一个文档包含一些字段和一个“营业时间”数组: 父结构如下所示: protocol RestaurantSerializable { init?(dictionary:[String:Any], restaurantId : String) } struct Restaurant { var distance: Double var distributionType : Int var businessHours : Array<

我的数据结构如下所示,其中一个文档包含一些字段和一个“营业时间”数组:

父结构如下所示:

protocol RestaurantSerializable {
    init?(dictionary:[String:Any], restaurantId : String)
}

struct Restaurant {
    var distance: Double
    var distributionType : Int
    var businessHours : Array<BusinessHours>

var dictionary: [String: Any] {
        return [
            "distance": distance,
            "distributionType": distributionType,
            "businessHours": businessHours.map({$0.dictionary})
        ]
    }
}

extension Restaurant : RestaurantSerializable {
    init?(dictionary: [String : Any], restaurantId: String) {
        guard let distance = dictionary["distance"] as? Double,
            let distributionType = dictionary["distributionType"] as? Int,
        let businessHours = dictionary["businessHours"] as? Array<BusinessHours>
        
            else { return nil }
         
self.init(distance: distance, geoPoint: geoPoint, distributionType: distributionType, businessHours, restaurantId : restaurantId)
       }
}
我正在尝试查询数据库,如下所示:

db.whereField("users", arrayContains: userId).getDocuments() { documentSnapshot, error in
            if let error = error {
                completion([], error.localizedDescription)
            } else {
                restaurantArray.append(contentsOf: (documentSnapshot?.documents.compactMap({ (restaurantDocument) -> Restaurant in
                    Restaurant(dictionary: restaurantDocument.data(), restaurantId: restaurantDocument.documentID)!
                }))!)
}
尽管我有数据,但在上面的最后一行,我不断地得到这个错误:

线程1:致命错误:在展开可选值时意外发现nil

如果我输入一个默认值,那么我得到的就是默认值。如何从平面JSON获取对象数组


我试图获得每个字段。然后通过businesshours字段进行解析,但这似乎效率低下。你知道我做错了什么吗?

我想问题出在你发布《营业时间》的地方:

让businessHours=dictionary[“businessHours”]as?排列
实际数据似乎是一个数组,但包含小时的字典,而不是
BusinessHours
obejcts。这就是guard失败的原因,
Restaurant
init返回
nil
,代码在展开时失败

我发现了一个更通用的字典序列化的良好实现,并在此基础上创建了适用于您的代码示例:

///一个协议,表示需要进行词汇编码的类型
协议字典可编码:可编码{
}
///实际实现双向字典编码的扩展
///通过JSON序列化
扩展字典可编码{
///如果编码成功,则返回可选字典
变量字典:[字符串:任何]{
guard let data=try?JSONEncoder().encode(self)else{
归零
}
将try?JSONSerialization.jsonObject(带:data,options:.allowFragments)返回为?[String:Any]
}
///创建从给定字典自解码的实例,或失败时为nil
静态func解码(来自字典:[字符串:任意])->Self{
guard let data=try?JSONSerialization.data(使用jsonObject:dictionary,options:.fragmentsAllowed)else{
归零
}
返回try?JSONDecoder().decode(Self.Self,from:data)
}
}
//您的结构现在没有要序列化或反序列化的特殊代码,
//但只需要符合DictionaryCodable协议
struct BusinessHours:DictionaryCodable{
所选变量:Bool
var thisDay:字符串
var startHour:Int
关闭时间:Int
}
结构餐厅:字典可容纳{
var距离:双
变量分配类型:Int
var营业时间:[营业时间]
}
//这是一家餐馆的例子
设r1=餐厅(距离:0.1,分布类型:1,营业时间:[
营业时间(选择:假,当天:“太阳”,开始时间:10,结束时间:23),
营业时间(选择:正确,当天:“周一”,开始时间:11,结束时间:18),
营业时间(选择:正确,当天:“星期二”,开始时间:11,结束时间:18),
])
//这就是它可以序列化的方式
guard let dictionary=r1.else{
打印(“错误编码对象”)
返回
}
//检查结果
印刷(字典)
//这就是如何将其直接反序列化到对象
守卫让r2=餐厅。解码(来自:字典)其他{
打印(“解码对象时出错”)
返回
}
//检查结果
打印(r2)
为了避免应用程序在强制展开时崩溃(最好不显示结果,而不是崩溃),我建议稍微更改用于从数据库检索数据的调用顺序:


db.whereField(“users”,arrayContains:userId).getDocuments(){documentSnapshot,出现错误
guard nil==错误else{
//我们可以在这里强制展开错误,因为它确实存在
完成([],错误!.localizedDescription)
返回
}
//compactMap将消除构造不当的餐厅实例,
//如果documentSnapshot为nil,则不会执行它
//您仅将非零餐厅实例附加到restaurantArray。
//最坏的情况是,你最终会得到一个不变的餐厅。
restaurantArray.append(内容为:documentSnapshot?.documents.compactMap{restaurantDocument in
Restaurant.decode(来自:restaurantDocument.data())
} ?? [])
}

创建
餐厅
实例时,
餐厅文档.data()
包含哪些内容?它是否包含
businessHours
字段,以及以何种形式?如果
BusinessHours
不是字典,则强制展开构造函数可以返回的
nil
对象。我建议尽可能避免强制展开,这样你的应用程序就不会崩溃。如果我不强制展开,我会得到一个空数组。原因只是你的可选值是
nil
,因此使用了提供的默认值。从最终用户的角度来看,空数组仍然比致命错误好得多:)一个文档的查询具有documentSnapshot,而集合的查询具有querySnapshot。(建议遵循默认值名称,因为“real”documentSnapshot没有文档属性)
db.whereField("users", arrayContains: userId).getDocuments() { documentSnapshot, error in
            if let error = error {
                completion([], error.localizedDescription)
            } else {
                restaurantArray.append(contentsOf: (documentSnapshot?.documents.compactMap({ (restaurantDocument) -> Restaurant in
                    Restaurant(dictionary: restaurantDocument.data(), restaurantId: restaurantDocument.documentID)!
                }))!)
}