Skip to content

Commit

Permalink
Firestore swift Codable support (#3198)
Browse files Browse the repository at this point in the history
FirestoreEncoder/Decoder and Timestamp/GeoPoint codable support.
  • Loading branch information
Hui-Wu authored and wilhuff committed Sep 13, 2019
1 parent 30e4937 commit 8ecec2b
Show file tree
Hide file tree
Showing 4 changed files with 1,240 additions and 4,826 deletions.
37 changes: 2 additions & 35 deletions Firestore/Swift/Source/Codable/third_party/FirestoreDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import Foundation

extension Firestore {
public struct Decoder {
fileprivate static let documentRefUserInfoKey = CodingUserInfoKey(rawValue: "DocumentRefUserInfoKey")

public init() {}
/// Returns an instance of specified type from a Firestore document.
///
Expand All @@ -28,22 +26,13 @@ extension Firestore {
/// in `container` are directly supported:
/// - GeoPoint
/// - Timestamp
/// - DocumentReference
///
/// - Parameters:
/// - A type to decode a document to.
/// - container: A Map keyed of String representing a Firestore document.
/// - document: A reference to the Firestore Document that is being
/// decoded.
/// - Returns: An instance of specified type by the first parameter.
public func decode<T: Decodable>(_: T.Type,
from container: [String: Any],
in document: DocumentReference? = nil) throws -> T {
public func decode<T: Decodable>(_: T.Type, from container: [String: Any]) throws -> T {
let decoder = _FirestoreDecoder(referencing: container)
if let doc = document {
decoder.userInfo[Firestore.Decoder.documentRefUserInfoKey!] = doc
}

guard let value = try decoder.unbox(container, as: T.self) else {
throw DecodingError.valueNotFound(
T.self,
Expand Down Expand Up @@ -333,24 +322,6 @@ private struct _FirestoreKeyedDecodingContainer<K: CodingKey>: KeyedDecodingCont
}

public func decode<T: Decodable>(_: T.Type, forKey key: Key) throws -> T {
if T.self == SelfDocumentID.self {
let docRef = decoder.userInfo[
Firestore.Decoder.documentRefUserInfoKey!
] as! DocumentReference?

if contains(key) {
let docPath = (docRef != nil) ? docRef!.path : "nil"
var codingPathCopy = codingPath.map { key in key.stringValue }
codingPathCopy.append(key.stringValue)

throw FirestoreDecodingError.fieldNameConfict("Field name " +
"\(codingPathCopy) was found from document \"\(docPath)\", " +
"cannot assign the document reference to this field.")
}

return SelfDocumentID(from: docRef) as! T
}

let entry = try require(key: key)

decoder.codingPath.append(key)
Expand Down Expand Up @@ -1006,10 +977,6 @@ extension _FirestoreDecoder {

func unbox(_ value: Any, as type: Date.Type) throws -> Date? {
guard !(value is NSNull) else { return nil }
// Firestore returns all dates as Timestamp, converting it to Date so it can be used in custom objects.
if let timestamp = value as? Timestamp {
return timestamp.dateValue()
}
guard let date = value as? Date else {
throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value)
}
Expand Down Expand Up @@ -1066,7 +1033,7 @@ extension _FirestoreDecoder {
}
}

// Decoding an embedded container, this requires expanding the storage stack and
// Decoding an embeded container, this requires expanding the storage stack and
// then restore after decoding.
storage.push(container: value)
let decoded = try T(from: self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,11 @@ extension Firestore {
/// The Firestore pass-through types are:
/// - GeoPoint
/// - Timestamp
/// - DocumentReference
///
/// - Parameter value: The Encodable object to convert to encoded data.
/// - Returns: A Map keyed by String representing a document Firestore
/// API can work with.
public func encode<T: Encodable>(_ value: T) throws -> [String: Any] {
// SelfDocumentID, DocumentReference and FieldValue cannot be
// encoded directly.
guard T.self != SelfDocumentID.self,
T.self != DocumentReference.self,
T.self != FieldValue.self else {
throw FirestoreEncodingError.encodingIsNotSupported
}
guard let topLevel = try _FirestoreEncoder().box_(value) else {
throw EncodingError.invalidValue(value,
EncodingError.Context(codingPath: [],
Expand Down Expand Up @@ -215,11 +207,6 @@ private struct _FirestoreKeyedEncodingContainer<K: CodingKey>: KeyedEncodingCont
public mutating func encode(_ value: Double, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }

public mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
// `SelfDocumentID` is ignored during encoding.
if T.self == SelfDocumentID.self {
return
}

encoder.codingPath.append(key)
defer {
encoder.codingPath.removeLast()
Expand Down Expand Up @@ -508,7 +495,7 @@ extension _FirestoreEncoder: SingleValueEncodingContainer {
}

/// Special subclass of `_FirestoreEncoder` used by `superEncoder`.
/// It inherits the codingPath from the referencing `_FirestoreEncoder` but uses its own
/// It inherits the codingPath from the referencing `_FirestoreEncoder` but uses it's own
/// storage. The encoded result will be written back to the referencing encoder's storage
/// when it is `deinit`-ed.
private class _FirestoreReferencingEncoder: _FirestoreEncoder {
Expand Down
Loading

0 comments on commit 8ecec2b

Please sign in to comment.