mirror of
https://github.com/caoer/CodableFirebase.git
synced 2026-04-01 22:35:55 +08:00
Merge pull request #14 from alickbass/geopoint-and-reference-encoding
Geopoint and reference encoding
This commit is contained in:
@@ -13,6 +13,7 @@ class _FirebaseDecoder : Decoder {
|
||||
struct _Options {
|
||||
let dateDecodingStrategy: FirebaseDecoder.DateDecodingStrategy?
|
||||
let dataDecodingStrategy: FirebaseDecoder.DataDecodingStrategy?
|
||||
let skipGeoPointAndReference: Bool
|
||||
let userInfo: [CodingUserInfoKey : Any]
|
||||
}
|
||||
|
||||
@@ -1229,6 +1230,8 @@ extension _FirebaseDecoder {
|
||||
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
|
||||
guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
|
||||
decoded = decimal as! T
|
||||
} else if options.skipGeoPointAndReference && (T.self is GeoPointType.Type || T.self is DocumentReferenceType.Type) {
|
||||
decoded = value as! T
|
||||
} else {
|
||||
self.storage.push(container: value)
|
||||
decoded = try T(from: self)
|
||||
|
||||
@@ -13,6 +13,7 @@ class _FirebaseEncoder : Encoder {
|
||||
struct _Options {
|
||||
let dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy?
|
||||
let dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy?
|
||||
let skipGeoPointAndReference: Bool
|
||||
let userInfo: [CodingUserInfoKey : Any]
|
||||
}
|
||||
|
||||
@@ -382,6 +383,11 @@ extension _FirebaseEncoder {
|
||||
return try self.box((value as! Data))
|
||||
} else if T.self == URL.self || T.self == NSURL.self {
|
||||
return self.box((value as! URL).absoluteString)
|
||||
} else if options.skipGeoPointAndReference && (value is GeoPointType || value is DocumentReferenceType) {
|
||||
guard let value = value as? NSObject else {
|
||||
throw DocumentReferenceError.typeIsNotNSObject
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// The value should request a container from the _FirebaseEncoder.
|
||||
|
||||
@@ -53,6 +53,7 @@ open class FirebaseDecoder {
|
||||
let options = _FirebaseDecoder._Options(
|
||||
dateDecodingStrategy: dateDecodingStrategy,
|
||||
dataDecodingStrategy: dataDecodingStrategy,
|
||||
skipGeoPointAndReference: false,
|
||||
userInfo: userInfo
|
||||
)
|
||||
let decoder = _FirebaseDecoder(referencing: container, options: options)
|
||||
|
||||
@@ -57,6 +57,7 @@ open class FirebaseEncoder {
|
||||
let options = _FirebaseEncoder._Options(
|
||||
dateEncodingStrategy: dateEncodingStrategy,
|
||||
dataEncodingStrategy: dataEncodingStrategy,
|
||||
skipGeoPointAndReference: false,
|
||||
userInfo: userInfo
|
||||
)
|
||||
let encoder = _FirebaseEncoder(options: options)
|
||||
|
||||
@@ -8,13 +8,26 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol GeoPointType: Codable {
|
||||
var latitude: Double { get }
|
||||
var longitude: Double { get }
|
||||
init(latitude: Double, longitude: Double)
|
||||
}
|
||||
|
||||
public protocol DocumentReferenceType: Codable {}
|
||||
|
||||
open class FirestoreDecoder {
|
||||
public init() {}
|
||||
|
||||
open var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
|
||||
open func decode<T : Decodable>(_ type: T.Type, from container: [String: Any]) throws -> T {
|
||||
let options = _FirebaseDecoder._Options(dateDecodingStrategy: nil, dataDecodingStrategy: nil, userInfo: userInfo)
|
||||
let options = _FirebaseDecoder._Options(
|
||||
dateDecodingStrategy: nil,
|
||||
dataDecodingStrategy: nil,
|
||||
skipGeoPointAndReference: true,
|
||||
userInfo: userInfo
|
||||
)
|
||||
let decoder = _FirebaseDecoder(referencing: container, options: options)
|
||||
guard let value = try decoder.unbox(container, as: T.self) else {
|
||||
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given dictionary was invalid"))
|
||||
@@ -23,3 +36,37 @@ open class FirestoreDecoder {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
enum GeoPointKeys: CodingKey {
|
||||
case latitude, longitude
|
||||
}
|
||||
|
||||
extension GeoPointType {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: GeoPointKeys.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: GeoPointKeys.self)
|
||||
try container.encode(latitude, forKey: .latitude)
|
||||
try container.encode(longitude, forKey: .longitude)
|
||||
}
|
||||
}
|
||||
|
||||
enum DocumentReferenceError: Error {
|
||||
case typeIsNotSupported
|
||||
case typeIsNotNSObject
|
||||
}
|
||||
|
||||
extension DocumentReferenceType {
|
||||
public init(from decoder: Decoder) throws {
|
||||
throw DocumentReferenceError.typeIsNotSupported
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
throw DocumentReferenceError.typeIsNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,12 @@ open class FirestoreEncoder {
|
||||
}
|
||||
|
||||
internal func encodeToTopLevelContainer<Value : Encodable>(_ value: Value) throws -> Any {
|
||||
let options = _FirebaseEncoder._Options(dateEncodingStrategy: nil, dataEncodingStrategy: nil, userInfo: userInfo)
|
||||
let options = _FirebaseEncoder._Options(
|
||||
dateEncodingStrategy: nil,
|
||||
dataEncodingStrategy: nil,
|
||||
skipGeoPointAndReference: true,
|
||||
userInfo: userInfo
|
||||
)
|
||||
let encoder = _FirebaseEncoder(options: options)
|
||||
guard let topLevel = try encoder.box_(value) else {
|
||||
throw EncodingError.invalidValue(value,
|
||||
|
||||
@@ -369,6 +369,19 @@ class TestCodableFirebase: XCTestCase {
|
||||
_testRoundTrip(of: 3 as Double)
|
||||
}
|
||||
|
||||
// MARK: - GeoPoint
|
||||
func testEncodingGeoPoint() {
|
||||
let point = Point(latitude: 2, longitude: 2)
|
||||
XCTAssertEqual((try? FirebaseEncoder().encode(point)) as? NSDictionary, ["latitude": 2, "longitude": 2])
|
||||
XCTAssertEqual(try? FirebaseDecoder().decode(Point.self, from: ["latitude": 2, "longitude": 2]), point)
|
||||
}
|
||||
|
||||
// MARK: - Document Reference
|
||||
func testEncodingDocumentReference() {
|
||||
XCTAssertThrowsError(try FirebaseEncoder().encode(DocumentReference()))
|
||||
XCTAssertThrowsError(try FirebaseDecoder().decode(DocumentReference.self, from: []))
|
||||
}
|
||||
|
||||
// MARK: - Helper Functions
|
||||
private var _emptyDictionary: [String: Any] = [:]
|
||||
|
||||
@@ -415,6 +428,19 @@ class TestCodableFirebase: XCTestCase {
|
||||
// MARK: - Test Types
|
||||
/* FIXME: Import from %S/Inputs/Coding/SharedTypes.swift somehow. */
|
||||
|
||||
// MARK: - GeoPoint
|
||||
struct Point: GeoPointType, Equatable {
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
|
||||
static func == (lhs: Point, rhs: Point) -> Bool {
|
||||
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ReferenceType
|
||||
fileprivate struct DocumentReference: DocumentReferenceType {}
|
||||
|
||||
// MARK: - Empty Types
|
||||
fileprivate struct EmptyStruct : Codable, Equatable {
|
||||
static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool {
|
||||
|
||||
@@ -109,6 +109,21 @@ class TestCodableFirestore: XCTestCase {
|
||||
_testRoundTrip(of: TopLevelWrapper(date), expected: ["value": date])
|
||||
}
|
||||
|
||||
// MARK: - GeoPoint & Document Reference
|
||||
func testEncodingGeoPoint() {
|
||||
let point = GeoPoint(latitude: 2, longitude: 2)
|
||||
let wrapper = TopLevelWrapper(point)
|
||||
XCTAssertEqual((try? FirestoreEncoder().encode(wrapper)) as NSDictionary?, ["value": point])
|
||||
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<GeoPoint>.self, from: ["value": point]), wrapper)
|
||||
XCTAssertThrowsError(try FirestoreEncoder().encode(TopLevelWrapper(Point(latitude: 2, longitude: 2))))
|
||||
}
|
||||
|
||||
func testEncodingDocumentReference() {
|
||||
let val = TopLevelWrapper(DocumentReference())
|
||||
XCTAssertEqual((try? FirestoreEncoder().encode(val)) as NSDictionary?, ["value": val.value])
|
||||
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<DocumentReference>.self, from: ["value": val.value]), val)
|
||||
}
|
||||
|
||||
private func _testEncodeFailure<T : Encodable>(of value: T) {
|
||||
do {
|
||||
let _ = try FirestoreEncoder().encode(value)
|
||||
@@ -164,3 +179,21 @@ func expectEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String)
|
||||
XCTAssertEqual(key1.stringValue, key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - GeioPoint
|
||||
fileprivate class GeoPoint: NSObject, GeoPointType {
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
|
||||
required init(latitude: Double, longitude: Double) {
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
}
|
||||
|
||||
static func == (lhs: Point, rhs: Point) -> Bool {
|
||||
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ReferenceType
|
||||
fileprivate class DocumentReference: NSObject, DocumentReferenceType {}
|
||||
|
||||
11
README.md
11
README.md
@@ -86,6 +86,17 @@ Firestore.firestore().collection("data").document("one").getDocument { (document
|
||||
}
|
||||
```
|
||||
|
||||
### How to use `GeoPoint` and `DocumentRefence` in Firestore
|
||||
|
||||
In order to use these 2 types with `Firestore`, you need to add the following code somewhere in your app:
|
||||
|
||||
```swift
|
||||
extension DocumentReference: DocumentReferenceType {}
|
||||
extension GeoPoint: GeoPointType {}
|
||||
```
|
||||
|
||||
and now they become `Codable` and can be used properly with `FirestoreEncoder` and `FirestoreDecoder`.
|
||||
|
||||
## Integration
|
||||
|
||||
### CocoaPods (iOS 9+)
|
||||
|
||||
Reference in New Issue
Block a user