【发布时间】:2018-09-27 11:25:48
【问题描述】:
我有一个 Firebase Firestore 文档,其中包含字符串、数字和 GeoPoint 值。这是 print() 函数打印的示例控制台输出。
[
"name": "Test",
"location": <FIRGeoPoint: (37.165300, 27.590800)>,
"aNumber": 123123
]
现在我想为这个文档创建一个结构,符合Codableprotocol。
struct TestStruct: Codable {
let name: String
let aNumber: Double
let location: GeoPoint
struct CodingKeys: CodingKey {
case name, location, aNumber
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: CodingKeys.name)
aNumber = try container.decode(Double.self, forKey: CodingKeys.aNumber)
location = try container.decode(GeoPoint.self, forKey: CodingKeys.location)
}
}
// encode is not implemented yet.
此代码将显示错误,因为 GeoPoint 不符合 Codable 协议。
所以我尝试让 GeoPoint 符合 Codable:
extension GeoPoint: Codable {
enum CodingKeys: CodingKey {
case latitute, longitude
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let latitude = try container.decode(Double.self, forKey: CodingKeys.latitute)
let longitude = try container.decode(Double.self, forKey: CodingKeys.latitute)
super.init(latitude: latitude, longitude: longitude)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: CodingKeys.latitute)
try container.encode(longitude, forKey: CodingKeys.longitude)
}
}
现在,IDE 生我的气了!
初始化器init(from:) 应该是必需的,但扩展不能有必需的初始化器。另外,扩展不能有指定的初始化器,所以初始化器也应该方便。一个愚蠢的死胡同。
为了绕过它,我将 GeoPoint 子类化:
class ANGeoPoint: GeoPoint, Codable {
enum CodingKeys: CodingKey {
case latitute, longitude
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let latitude = try container.decode(Double.self, forKey: CodingKeys.latitute)
let longitude = try container.decode(Double.self, forKey: CodingKeys.latitute)
super.init(latitude: latitude, longitude: longitude)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: CodingKeys.latitute)
try container.encode(longitude, forKey: CodingKeys.longitude)
}
}
改变了
location = try container.decode(GeoPoint.self, forKey: CodingKeys.location)
行到
location = try container.decode(ANGeoPoint.self, forKey: CodingKeys.location)
现在代码中没有 IDE 警告。 让我们测试一下:
Firestore.firestore()
.collection("testCollection")
.document("qweasdzxc")
.getDocument { (snap, error) in
if let data = snap?.data() {
let jsonData = JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
let myStruct = try? JSONDecoder().decode(TestStruct.self, from: jsonData)
}
}
当我们运行我们的测试代码时,它会崩溃!这是我全新的婴儿运行时错误:
由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“JSON 写入中的类型无效 (FIRGeoPoint)”
让我们回顾一下示例数据的控制台输出:
...
"location": <FIRGeoPoint: (37.165300, 27.590800)>
...
即使我尝试将location 解码为 ANGeoPoint,同时将数据解码为 TestStruct,来自 Firestore 的位置数据仍然是 GeoPoint。并且 JSONDecode 无法解码非 Codable 对象。
更多,你还记得 Xcode 不要让我创建 Codable GeoPoint。
现在我被困住了!有什么建议?谢谢。
编辑:我在 Firebase iOS SDK 中找到了这个:https://github.com/firebase/firebase-ios-sdk/commit/13e366738463739f0c21d4cedab4bafbfdb57c6f
但即使我使用的是最新版本,我的代码也没有这个。所以我手动添加了:
protocol CodableGeoPoint: Codable {
var latitude: Double { get }
var longitude: Double { get }
init(latitude: Double, longitude: Double)
}
extension CodableGeoPoint {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let latitude = try container.decode(Double.self, forKey: CodingKeys.makeKey(name: "latitude"))
let longitude = try container.decode(Double.self, forKey: CodingKeys.makeKey(name: "longitude"))
self.init(latitude: latitude, longitude: longitude)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: CodingKeys.makeKey(name: "latitude"))
try container.encode(longitude, forKey: CodingKeys.makeKey(name: "longitude"))
}
}
extension GeoPoint: CodableGeoPoint {}
现在GeoPoint 是可编码的。但是我仍然无法使用 JSONDecoder 对其进行解码。
【问题讨论】:
-
如果问题是您尝试使用
JSONDecoder来解码无效的JSON(例如"location": <FIRGeoPoint: (37.165300, 27.590800)>),我认为您需要在JSONDecode 之前预先解析数据,并转换@ 987654338@ 变成"location": {"lat":32.2, "lon":28.9} -
我想过,但是如果我应该预先解析数据,编码或解码数据的含义是什么。我可以手动将所有内容解析到结构中。
-
数据是如何以这种格式进入 Firestore 的?你能改变它的存储格式吗?
-
正如我所指定的,当您使用
print(_:)函数将数据打印到控制台时,它是数据的表示。这是一个[String: Any]?字典。下面是 DocumentSnapshot 类的参考:https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/DocumentSnapshot
标签: ios swift firebase google-cloud-firestore codable