| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- // This file is derived from swift/stdlib/public/SDK/Foundation/JSONEncoder.swift
- // and swift/stdlib/public/SDK/Foundation/PlistEncoder.swift
- //===----------------------------------------------------------------------===//
- //
- // This source file is part of the Swift.org open source project
- //
- // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
- // Licensed under Apache License v2.0 with Runtime Library Exception
- //
- // See https://swift.org/LICENSE.txt for license information
- // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
- //
- //===----------------------------------------------------------------------===//
- import FirebaseFirestore
- import Foundation
- extension Firestore {
- public struct Encoder {
- public init() {}
- /// Returns encoded data that Firestore API recognizes.
- ///
- /// If possible, all types will be converted to compatible types Firestore
- /// can handle. This means certain Firestore specific types will be encoded
- /// as pass-through: this encoder will only pass those types along since that
- /// is what Firestore can handle. The same types will be encoded differently
- /// with other encoders (for example: JSONEncoder).
- ///
- /// 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 != DocumentReference.self,
- T.self != FieldValue.self else {
- throw EncodingError
- .invalidValue(value,
- EncodingError
- .Context(codingPath: [],
- debugDescription: "Top-level \(T.self) is not allowed."))
- }
- guard let topLevel = try _FirestoreEncoder().box_(value) else {
- throw EncodingError.invalidValue(value,
- EncodingError.Context(codingPath: [],
- debugDescription: "Top-level \(T.self) did not encode any values."))
- }
- // This is O(n) check. Consider refactoring box_ to return [String: Any].
- guard let dict = topLevel as? [String: Any] else {
- throw EncodingError.invalidValue(value,
- EncodingError.Context(codingPath: [],
- debugDescription: "Top-level \(T.self) encoded not as dictionary."))
- }
- return dict
- }
- }
- }
- private class _FirestoreEncoder: Encoder {
- /// A stack of data containers storing encoded results. When a new object is being encoded,
- /// a corresponding storage is pushed to the stack; and when the field and all of its children objects
- /// are encoded, the stoage should have the entire encoded result for the sub-tree, it is then poped
- /// out and written to the proper place of the new stack top container by referencing to the top of `codingPath`.
- fileprivate var storage: _FirestoreEncodingStorage
- /// An array used as a stack to keep track of where in the encoded data tree the encoder is trying to process
- /// at the moment.
- public fileprivate(set) var codingPath: [CodingKey]
- public var userInfo: [CodingUserInfoKey: Any] = [:]
- init() {
- storage = _FirestoreEncodingStorage()
- codingPath = []
- }
- /// Returns whether we should allocate new container to store encoded result for the current
- /// codingPath.
- ///
- /// `true` if no container has been allocated for this coding path; `false` otherwise.
- fileprivate var shouldAllocateNewContainer: Bool {
- // The encoder starts with storage.count == codingPath.count == 0, which means it is encoding the root
- // object without any container allocated, so when a container is requested, it must be allocated
- // first. When a container is requested again with the same codingPath however, for another field from the root object possibly,
- // because now storage.count == codingPath + 1, we should not allocate new containers because there are
- // already encoded results in the container. This relation between the two stacks is maintained
- // throughout the encoding process.
- return storage.count == codingPath.count
- }
- // MARK: - Encoder Methods
- public func container<Key>(keyedBy _: Key.Type) -> KeyedEncodingContainer<Key> {
- // If an existing keyed container was already requested, return that one.
- let topContainer: NSMutableDictionary
- if shouldAllocateNewContainer {
- // We haven't yet pushed a container at this level; do so here.
- topContainer = storage.pushKeyedContainer()
- } else {
- guard let container = storage.containers.last as? NSMutableDictionary else {
- preconditionFailure(
- "Attempt to push new keyed encoding container when already previously encoded at this path."
- )
- }
- topContainer = container
- }
- let container = _FirestoreKeyedEncodingContainer<Key>(referencing: self, codingPath: codingPath,
- wrapping: topContainer)
- return KeyedEncodingContainer(container)
- }
- public func unkeyedContainer() -> UnkeyedEncodingContainer {
- // If an existing unkeyed container was already requested, return that one.
- let topContainer: NSMutableArray
- if shouldAllocateNewContainer {
- // We haven't yet pushed a container at this level; do so here.
- topContainer = storage.pushUnkeyedContainer()
- } else {
- guard let container = storage.containers.last as? NSMutableArray else {
- preconditionFailure(
- "Attempt to push new unkeyed encoding container when already previously encoded at this path."
- )
- }
- topContainer = container
- }
- return _FirestoreUnkeyedEncodingContainer(referencing: self, codingPath: codingPath,
- wrapping: topContainer)
- }
- public func singleValueContainer() -> SingleValueEncodingContainer {
- return self
- }
- }
- private struct _FirestoreEncodingStorage {
- // MARK: Properties
- /// The container stack.
- /// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary).
- fileprivate private(set) var containers: [NSObject] = []
- // MARK: - Initialization
- /// Initializes `self` with no containers.
- fileprivate init() {}
- // MARK: - Modifying the Stack
- fileprivate var count: Int {
- return containers.count
- }
- fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary {
- let dictionary = NSMutableDictionary()
- containers.append(dictionary)
- return dictionary
- }
- fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray {
- let array = NSMutableArray()
- containers.append(array)
- return array
- }
- fileprivate mutating func push(container: NSObject) {
- containers.append(container)
- }
- fileprivate mutating func popContainer() -> NSObject {
- precondition(containers.count > 0, "Empty container stack.")
- let ret = containers.popLast()!
- return ret
- }
- }
- private struct _FirestoreKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol {
- typealias Key = K
- // MARK: Properties
- /// A reference to the encoder we're writing to.
- private let encoder: _FirestoreEncoder
- /// A reference to the container we're writing to.
- private let container: NSMutableDictionary
- /// The path of coding keys taken to get to this point in encoding.
- public private(set) var codingPath: [CodingKey]
- // MARK: - Initialization
- /// Initializes `self` with the given references.
- fileprivate init(referencing encoder: _FirestoreEncoder,
- codingPath: [CodingKey],
- wrapping container: NSMutableDictionary) {
- self.encoder = encoder
- self.codingPath = codingPath
- self.container = container
- }
- // MARK: - KeyedEncodingContainerProtocol Methods
- public mutating func encodeNil(forKey key: Key) throws { container[key.stringValue] = NSNull() }
- public mutating func encode(_ value: Bool, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: Int, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: Int8, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: Int16, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: Int32, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: Int64, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: UInt, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: UInt8, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: UInt16, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: UInt32, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: UInt64, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: String, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- public mutating func encode(_ value: Float, forKey key: Key) throws {
- container[key.stringValue] = encoder.box(value)
- }
- 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 {
- #if compiler(>=5.1)
- // `DocumentID`-annotated fields are ignored during encoding.
- if T.self is DocumentIDProtocol.Type {
- return
- }
- #endif // compiler(>=5.1)
- encoder.codingPath.append(key)
- defer {
- encoder.codingPath.removeLast()
- }
- container[key.stringValue] = try encoder.box(value)
- }
- public mutating func nestedContainer<NestedKey>(keyedBy _: NestedKey.Type,
- forKey key: Key)
- -> KeyedEncodingContainer<NestedKey> {
- let dictionary = NSMutableDictionary()
- self.container[key.stringValue] = dictionary
- codingPath.append(key)
- defer {
- codingPath.removeLast()
- }
- let container = _FirestoreKeyedEncodingContainer<NestedKey>(referencing: encoder,
- codingPath: codingPath,
- wrapping: dictionary)
- return KeyedEncodingContainer(container)
- }
- public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
- let array = NSMutableArray()
- container[key.stringValue] = array
- codingPath.append(key)
- defer {
- codingPath.removeLast()
- }
- return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath,
- wrapping: array)
- }
- public mutating func superEncoder() -> Encoder {
- return _FirestoreReferencingEncoder(referencing: encoder, at: _FirestoreKey.super,
- wrapping: container)
- }
- public mutating func superEncoder(forKey key: Key) -> Encoder {
- return _FirestoreReferencingEncoder(referencing: encoder, at: key, wrapping: container)
- }
- }
- private struct _FirestoreUnkeyedEncodingContainer: UnkeyedEncodingContainer {
- // MARK: Properties
- /// A reference to the encoder we're writing to.
- private let encoder: _FirestoreEncoder
- /// A reference to the container we're writing to.
- private let container: NSMutableArray
- /// The path of coding keys taken to get to this point in encoding.
- public private(set) var codingPath: [CodingKey]
- /// The number of elements encoded into the container.
- public var count: Int {
- return container.count
- }
- // MARK: - Initialization
- /// Initializes `self` with the given references.
- fileprivate init(referencing encoder: _FirestoreEncoder,
- codingPath: [CodingKey],
- wrapping container: NSMutableArray) {
- self.encoder = encoder
- self.codingPath = codingPath
- self.container = container
- }
- // MARK: - UnkeyedEncodingContainer Methods
- public mutating func encodeNil() throws { container.add(NSNull()) }
- public mutating func encode(_ value: Bool) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: Int) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: Int8) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: Int16) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: Int32) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: Int64) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: UInt) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: UInt8) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: UInt16) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: UInt32) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: UInt64) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: Float) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: Double) throws { container.add(encoder.box(value)) }
- public mutating func encode(_ value: String) throws { container.add(encoder.box(value)) }
- public mutating func encode<T: Encodable>(_ value: T) throws {
- encoder.codingPath.append(_FirestoreKey(index: count))
- defer { encoder.codingPath.removeLast()
- }
- container.add(try encoder.box(value))
- }
- public mutating func nestedContainer<NestedKey>(keyedBy _: NestedKey
- .Type) -> KeyedEncodingContainer<NestedKey> {
- codingPath.append(_FirestoreKey(index: count))
- defer {
- self.codingPath.removeLast()
- }
- let dictionary = NSMutableDictionary()
- self.container.add(dictionary)
- let container = _FirestoreKeyedEncodingContainer<NestedKey>(referencing: encoder,
- codingPath: codingPath,
- wrapping: dictionary)
- return KeyedEncodingContainer(container)
- }
- public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
- codingPath.append(_FirestoreKey(index: count))
- defer {
- self.codingPath.removeLast()
- }
- let array = NSMutableArray()
- container.add(array)
- return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath,
- wrapping: array)
- }
- public mutating func superEncoder() -> Encoder {
- return _FirestoreReferencingEncoder(referencing: encoder, at: container.count,
- wrapping: container)
- }
- }
- struct _FirestoreKey: CodingKey {
- public var stringValue: String
- public var intValue: Int?
- public init?(stringValue: String) {
- self.stringValue = stringValue
- intValue = nil
- }
- public init?(intValue: Int) {
- stringValue = "\(intValue)"
- self.intValue = intValue
- }
- init(index: Int) {
- stringValue = "Index \(index)"
- intValue = index
- }
- static let `super` = _FirestoreKey(stringValue: "super")!
- }
- extension _FirestoreEncoder {
- /// Returns the given value boxed in a container appropriate for pushing onto the container stack.
- fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) }
- fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) }
- fileprivate func box<T: Encodable>(_ value: T) throws -> NSObject {
- return try box_(value) ?? NSDictionary()
- }
- func box_<T: Encodable>(_ value: T) throws -> NSObject? {
- if T.self == Date.self || T.self == NSDate.self {
- return (value as! NSDate)
- } else if T.self == Data.self || T.self == NSData.self {
- return (value as! NSData)
- } else if T.self == URL.self || T.self == NSURL.self {
- return box((value as! URL).absoluteString)
- } else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
- return (value as! NSDecimalNumber)
- } else if isFirestorePassthroughType(value) {
- // This is a native Firestore types that we don't need to encode.
- return (value as! NSObject)
- }
- // The value should request a container from the _FirestoreEncoder.
- let depth = storage.count
- do {
- try value.encode(to: self)
- } catch {
- // If the value pushed a container before throwing, pop it back off to restore state.
- if storage.count > depth {
- _ = storage.popContainer()
- }
- throw error
- }
- // The top container should be a new container.
- guard storage.count > depth else {
- return nil
- }
- return storage.popContainer()
- }
- }
- extension _FirestoreEncoder: SingleValueEncodingContainer {
- // MARK: - SingleValueEncodingContainer Methods
- private func assertCanEncodeNewValue() {
- precondition(shouldAllocateNewContainer,
- "Attempt to encode value through single value container when previously value already encoded.")
- }
- public func encodeNil() throws {
- assertCanEncodeNewValue()
- storage.push(container: NSNull())
- }
- public func encode(_ value: Bool) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: Int) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: Int8) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: Int16) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: Int32) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: Int64) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: UInt) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: UInt8) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: UInt16) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: UInt32) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: UInt64) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: String) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: Float) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode(_ value: Double) throws {
- assertCanEncodeNewValue()
- storage.push(container: box(value))
- }
- public func encode<T: Encodable>(_ value: T) throws {
- assertCanEncodeNewValue()
- try storage.push(container: box(value))
- }
- }
- /// Special subclass of `_FirestoreEncoder` used by `superEncoder`.
- /// It inherits the codingPath from the referencing `_FirestoreEncoder` but uses its own
- /// storage. The encoded result will be written back to the referencing encoder's storage
- /// when it is `deinit`-ed.
- private class _FirestoreReferencingEncoder: _FirestoreEncoder {
- // MARK: Reference types.
- /// The type of container we're referencing, and where to write the encoded result to.
- private enum Reference {
- /// Referencing a specific index in an array container.
- case array(NSMutableArray, Int)
- /// Referencing a specific key in a dictionary container.
- case dictionary(NSMutableDictionary, String)
- }
- // MARK: - Properties
- /// The encoder we're referencing.
- private let encoder: _FirestoreEncoder
- /// The container reference itself.
- private let reference: Reference
- // MARK: - Initialization
- /// Initializes `self` by referencing the given array container in the given encoder.
- fileprivate init(referencing encoder: _FirestoreEncoder,
- at index: Int,
- wrapping array: NSMutableArray) {
- self.encoder = encoder
- reference = .array(array, index)
- super.init()
- codingPath = encoder.codingPath
- codingPath.append(_FirestoreKey(index: index))
- }
- /// Initializes `self` by referencing the given dictionary container in the given encoder.
- fileprivate init(referencing encoder: _FirestoreEncoder,
- at key: CodingKey,
- wrapping dictionary: NSMutableDictionary) {
- self.encoder = encoder
- reference = .dictionary(dictionary, key.stringValue)
- super.init()
- codingPath = encoder.codingPath
- codingPath.append(key)
- }
- // MARK: - Coding Path Operations
- override fileprivate var shouldAllocateNewContainer: Bool {
- // With a regular encoder, the storage and coding path grow together.
- // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for.
- // We have to take this into account.
- return storage.count == codingPath.count - encoder.codingPath.count - 1
- }
- // MARK: - Deinitialization
- // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage.
- deinit {
- let value: Any
- switch storage.count {
- case 0: value = NSDictionary()
- case 1: value = self.storage.popContainer()
- default: fatalError("Referencing encoder deallocated with multiple containers on stack.")
- }
- switch self.reference {
- case let .array(array, index):
- array.insert(value, at: index)
- case let .dictionary(dictionary, key):
- dictionary[NSString(string: key)] = value
- }
- }
- }
|