我有一个Firebase Firestore文档,其中包含String,Number和GeoPoint值。这是由print()函数打印的示例控制台输出。
[
"name": "Test",
"location": <FIRGeoPoint: (37.165300, 27.590800)>,
"aNumber": 123123
]
现在我想为这个文档创建一个符合Codable
协议的结构。
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)
}
}
当我们运行我们的测试代码时,它会破坏!这是我全新的婴儿运行时错误:
由于未捕获的异常终止应用程序&#39; NSInvalidArgumentException&#39;,原因:&#39; JSON写入中的无效类型(FIRGeoPoint)&#39;
让我们回顾一下示例数据的控制台输出:
...
"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
是Codable。但我仍然无法用JSONDecoder解码它。
答案 0 :(得分:1)
Google确实发布了 FirebaseFirestoreSwift 吊舱,该吊舱针对所有类型(包括 GeoPoint )实现了 Codable 协议。
答案 1 :(得分:0)
以下是使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)
}
enum CodableGeoPointCodingKeys: CodingKey {
case latitude, longitude
}
extension CodableGeoPoint {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodableGeoPointCodingKeys.self)
let latitude = try container.decode(Double.self, forKey: .latitude)
let longitude = try container.decode(Double.self, forKey: .longitude)
self.init(latitude: latitude, longitude: longitude)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodableGeoPointCodingKeys.self)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)
}
}
extension GeoPoint: CodableGeoPoint {}
由于GeoPoint保存在[String:Any]中?作为一个对象,JSONSerilalization只能保存字符串和数字,你不能将数据序列化为json。
首先,您必须使用JSONSerialization create和jsonObject(aka Dictionary)对GeoPoint进行编码,并使用此jsonObject替换GeoPoint,然后您就可以将数据解码为Struct。这也适用于Timestamp对象。
以下是我所说的代码表示:
Firestore.firestore()
.collection("testCollection")
.document("qweasdzxc")
.getDocument { (snap, error) in
if var data = snap?.data() {
// check every key's value if it is a GeoPoint. If it is, convert it into Dictionary. You have to do these for inner values too.
for key in data.keys {
if let val = data[key] as? GeoPoint {
let locData = try JSONEncoder().encode(val)
data[key] = try JSONSerialization.jsonObject(with: locData, options: .allowFragments)
}
}
let jsonData = JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
let myStruct = try? JSONDecoder().decode(TestStruct.self, from: jsonData)
}
}
另一种解决方案是将GeoPoint保存在变量中并从数据中删除它。完成序列化和解码后,您可以手动将结构的GeoPoint数据设置为您持有的数据。
这是我头痛2天后的最佳解决方案。希望有人能找到更好的并在这里分享。