mirror of
https://github.com/caoer/CodableFirebase.git
synced 2026-06-15 09:27:41 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
095d37e1c4 | ||
|
|
2f2d50a065 | ||
|
|
81007b2998 | ||
|
|
c907950c82 | ||
|
|
1319fdb902 | ||
|
|
6d6ad92bee | ||
|
|
49299dd857 | ||
|
|
df13646d4d | ||
|
|
b83a25bf60 | ||
|
|
4b967b2c91 | ||
|
|
c999e5375d |
@@ -1,5 +1,5 @@
|
||||
language: objective-c
|
||||
osx_image: xcode9.3
|
||||
osx_image: xcode10.0
|
||||
|
||||
env:
|
||||
- ACTION=test PLATFORM=Mac DESTINATION='platform=OS X'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CodableFirebase"
|
||||
s.version = "0.2.0"
|
||||
s.version = "0.2.1"
|
||||
s.summary = "Use Codable with Firebase"
|
||||
s.description = "This library helps you use your custom models that conform to Codable protocol with Firebase Realtime Database and Firestore"
|
||||
s.homepage = "https://github.com/alickbass/CodableFirebase"
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
09D19A4B218D64F900A862A3 /* DecodeStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D19A4A218D64F900A862A3 /* DecodeStrategy.swift */; };
|
||||
09D19A4D218D650000A862A3 /* EncodeStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D19A4C218D650000A862A3 /* EncodeStrategy.swift */; };
|
||||
09D19A4E218D874000A862A3 /* FirestoreDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */; };
|
||||
09D19A4F218D88A800A862A3 /* FirestoreEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */; };
|
||||
CE7DD3711F9CFA81000225C5 /* CodableFirebase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE7DD3671F9CFA81000225C5 /* CodableFirebase.framework */; };
|
||||
CE7DD3781F9CFA81000225C5 /* CodableFirebase.h in Headers */ = {isa = PBXBuildFile; fileRef = CE7DD36A1F9CFA81000225C5 /* CodableFirebase.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */; };
|
||||
CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */; };
|
||||
CE7DD3861F9DE4F7000225C5 /* TestCodableFirestore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */; };
|
||||
CEFDBF821FF3B35B00745EBE /* Encoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF811FF3B35B00745EBE /* Encoder.swift */; };
|
||||
CEFDBF861FF3B56200745EBE /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF851FF3B56200745EBE /* Decoder.swift */; };
|
||||
@@ -31,6 +33,8 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
09D19A4A218D64F900A862A3 /* DecodeStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodeStrategy.swift; sourceTree = "<group>"; };
|
||||
09D19A4C218D650000A862A3 /* EncodeStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodeStrategy.swift; sourceTree = "<group>"; };
|
||||
CE7DD3671F9CFA81000225C5 /* CodableFirebase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CodableFirebase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CE7DD36A1F9CFA81000225C5 /* CodableFirebase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CodableFirebase.h; sourceTree = "<group>"; };
|
||||
CE7DD36B1F9CFA81000225C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -92,6 +96,8 @@
|
||||
CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */,
|
||||
CEFDBF891FF3E24200745EBE /* FirebaseDecoder.swift */,
|
||||
CEFDBF8B1FF3E3CB00745EBE /* FirebaseEncoder.swift */,
|
||||
09D19A4A218D64F900A862A3 /* DecodeStrategy.swift */,
|
||||
09D19A4C218D650000A862A3 /* EncodeStrategy.swift */,
|
||||
CEFDBF851FF3B56200745EBE /* Decoder.swift */,
|
||||
CEFDBF811FF3B35B00745EBE /* Encoder.swift */,
|
||||
CE7DD36B1F9CFA81000225C5 /* Info.plist */,
|
||||
@@ -177,6 +183,7 @@
|
||||
};
|
||||
CE7DD36F1F9CFA81000225C5 = {
|
||||
CreatedOnToolsVersion = 9.0.1;
|
||||
LastSwiftMigration = 1000;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
@@ -221,11 +228,13 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */,
|
||||
09D19A4D218D650000A862A3 /* EncodeStrategy.swift in Sources */,
|
||||
CEFDBF8C1FF3E3CB00745EBE /* FirebaseEncoder.swift in Sources */,
|
||||
09D19A4B218D64F900A862A3 /* DecodeStrategy.swift in Sources */,
|
||||
09D19A4E218D874000A862A3 /* FirestoreDecoder.swift in Sources */,
|
||||
09D19A4F218D88A800A862A3 /* FirestoreEncoder.swift in Sources */,
|
||||
CEFDBF821FF3B35B00745EBE /* Encoder.swift in Sources */,
|
||||
CEFDBF861FF3B56200745EBE /* Decoder.swift in Sources */,
|
||||
CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */,
|
||||
CEFDBF8A1FF3E24200745EBE /* FirebaseDecoder.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -389,7 +398,7 @@
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator macosx";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3,4";
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 2.0;
|
||||
@@ -418,7 +427,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator macosx";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3,4";
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 2.0;
|
||||
@@ -441,7 +450,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos watchos macosx appletvsimulator iphonesimulator watchsimulator";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3,4";
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 2.0;
|
||||
@@ -464,7 +473,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos watchos macosx appletvsimulator iphonesimulator watchsimulator";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3,4";
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 2.0;
|
||||
|
||||
77
CodableFirebase/DecodeStrategy.swift
Normal file
77
CodableFirebase/DecodeStrategy.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// DecodeStrategy.swift
|
||||
// CodableFirebase
|
||||
//
|
||||
// Created by Zitao Xiong on 11/3/18.
|
||||
// Copyright © 2018 ViolentOctopus. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The strategy to use for decoding `Date` values.
|
||||
public enum DateDecodingStrategy {
|
||||
/// Defer to `Date` for decoding. This is the default strategy.
|
||||
case deferredToDate
|
||||
|
||||
case deferredToTimestamp
|
||||
|
||||
/// Decode the `Date` as a UNIX timestamp from a JSON number.
|
||||
case secondsSince1970
|
||||
|
||||
/// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
|
||||
case millisecondsSince1970
|
||||
|
||||
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
|
||||
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
||||
case iso8601
|
||||
|
||||
/// Decode the `Date` as a string parsed by the given formatter.
|
||||
case formatted(DateFormatter)
|
||||
|
||||
/// Decode the `Date` as a custom value decoded by the given closure.
|
||||
case custom((_ decoder: Decoder) throws -> Date)
|
||||
}
|
||||
|
||||
/// The strategy to use for decoding `Data` values.
|
||||
public enum DataDecodingStrategy {
|
||||
/// Defer to `Data` for decoding.
|
||||
case deferredToData
|
||||
|
||||
/// Decode the `Data` from a Base64-encoded string. This is the default strategy.
|
||||
case base64
|
||||
|
||||
/// Decode the `Data` as a custom value decoded by the given closure.
|
||||
case custom((_ decoder: Decoder) throws -> Data)
|
||||
}
|
||||
|
||||
public enum FirestoreTypeDecodingStrategy {
|
||||
case deferredToPtotocol
|
||||
case custom((_ value: Any) throws -> Any)
|
||||
}
|
||||
|
||||
|
||||
extension CodingUserInfoKey {
|
||||
public static let dateDecodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "dateDecodingStrategy")!
|
||||
|
||||
public static let dataDecodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "dataDecodingStrategy")!
|
||||
|
||||
public static let firestoreTypeDecodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "firestoreTypeDecodingStrategy")!
|
||||
}
|
||||
|
||||
extension Dictionary where Key == CodingUserInfoKey, Value == Any {
|
||||
var dateDecodingStrategy: DateDecodingStrategy? {
|
||||
return self[.dateDecodingStrategy] as? DateDecodingStrategy
|
||||
}
|
||||
|
||||
var dataDecodingStrategy: DataDecodingStrategy? {
|
||||
return self[.dataDecodingStrategy] as? DataDecodingStrategy
|
||||
}
|
||||
|
||||
var firestoreTypeDecodingStrategy: FirestoreTypeDecodingStrategy {
|
||||
if let strategy = self[.firestoreTypeDecodingStrategy] as? FirestoreTypeDecodingStrategy {
|
||||
return strategy
|
||||
}
|
||||
|
||||
return FirestoreTypeDecodingStrategy.deferredToPtotocol
|
||||
}
|
||||
}
|
||||
@@ -9,35 +9,22 @@
|
||||
import Foundation
|
||||
|
||||
class _FirebaseDecoder : Decoder {
|
||||
/// Options set on the top-level encoder to pass down the decoding hierarchy.
|
||||
struct _Options {
|
||||
let dateDecodingStrategy: FirebaseDecoder.DateDecodingStrategy?
|
||||
let dataDecodingStrategy: FirebaseDecoder.DataDecodingStrategy?
|
||||
let skipFirestoreTypes: Bool
|
||||
let userInfo: [CodingUserInfoKey : Any]
|
||||
}
|
||||
|
||||
// MARK: Properties
|
||||
/// The decoder's storage.
|
||||
fileprivate var storage: _FirebaseDecodingStorage
|
||||
|
||||
fileprivate let options: _Options
|
||||
|
||||
/// The path to the current point in encoding.
|
||||
fileprivate(set) public var codingPath: [CodingKey]
|
||||
|
||||
/// Contextual user-provided information for use during encoding.
|
||||
public var userInfo: [CodingUserInfoKey : Any] {
|
||||
return options.userInfo
|
||||
}
|
||||
|
||||
let userInfo: [CodingUserInfoKey : Any]
|
||||
|
||||
// MARK: - Initialization
|
||||
/// Initializes `self` with the given top-level container and options.
|
||||
init(referencing container: Any, at codingPath: [CodingKey] = [], options: _Options) {
|
||||
init(referencing container: Any, at codingPath: [CodingKey] = [], userInfo: [CodingUserInfoKey: Any]) {
|
||||
self.storage = _FirebaseDecodingStorage()
|
||||
self.storage.push(container: container)
|
||||
self.codingPath = codingPath
|
||||
self.options = options
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
// MARK: - Decoder Methods
|
||||
@@ -410,7 +397,7 @@ fileprivate struct _FirebaseKeyedDecodingContainer<K : CodingKey> : KeyedDecodin
|
||||
defer { self.decoder.codingPath.removeLast() }
|
||||
|
||||
let value: Any = container[key.stringValue] ?? NSNull()
|
||||
return _FirebaseDecoder(referencing: value, at: self.decoder.codingPath, options: decoder.options)
|
||||
return _FirebaseDecoder(referencing: value, at: self.decoder.codingPath, userInfo: decoder.userInfo)
|
||||
}
|
||||
|
||||
public func superDecoder() throws -> Decoder {
|
||||
@@ -771,7 +758,7 @@ fileprivate struct _FirebaseUnkeyedDecodingContainer : UnkeyedDecodingContainer
|
||||
|
||||
let value = self.container[self.currentIndex]
|
||||
self.currentIndex += 1
|
||||
return _FirebaseDecoder(referencing: value, at: decoder.codingPath, options: decoder.options)
|
||||
return _FirebaseDecoder(referencing: value, at: decoder.codingPath, userInfo: decoder.userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1109,7 +1096,7 @@ extension _FirebaseDecoder {
|
||||
func unbox(_ value: Any, as type: Date.Type) throws -> Date? {
|
||||
guard !(value is NSNull) else { return nil }
|
||||
|
||||
guard let options = options.dateDecodingStrategy else {
|
||||
guard let options = userInfo.dateDecodingStrategy else {
|
||||
guard let date = value as? Date else {
|
||||
throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value)
|
||||
}
|
||||
@@ -1117,6 +1104,9 @@ extension _FirebaseDecoder {
|
||||
}
|
||||
|
||||
switch options {
|
||||
case .deferredToTimestamp:
|
||||
let timestamp = value as! TimestampType
|
||||
return timestamp.dateValue()
|
||||
case .deferredToDate:
|
||||
self.storage.push(container: value)
|
||||
let date = try Date(from: self)
|
||||
@@ -1162,7 +1152,7 @@ extension _FirebaseDecoder {
|
||||
func unbox(_ value: Any, as type: Data.Type) throws -> Data? {
|
||||
guard !(value is NSNull) else { return nil }
|
||||
|
||||
guard let options = options.dataDecodingStrategy else {
|
||||
guard let options = userInfo.dataDecodingStrategy else {
|
||||
guard let data = value as? Data else {
|
||||
throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value)
|
||||
}
|
||||
@@ -1230,9 +1220,17 @@ 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.skipFirestoreTypes && (T.self is FirestoreDecodable.Type) {
|
||||
decoded = value as! T
|
||||
} else {
|
||||
}
|
||||
else if userInfo.skipFirestoreTypes && (T.self is FirestoreDecodable.Type) {
|
||||
let strategy = userInfo.firestoreTypeDecodingStrategy
|
||||
switch strategy {
|
||||
case .deferredToPtotocol:
|
||||
decoded = value as! T
|
||||
case .custom(let decodeFunc):
|
||||
decoded = try decodeFunc(value) as! T
|
||||
}
|
||||
}
|
||||
else {
|
||||
self.storage.push(container: value)
|
||||
decoded = try T(from: self)
|
||||
self.storage.popContainer()
|
||||
|
||||
89
CodableFirebase/EncodeStrategy.swift
Normal file
89
CodableFirebase/EncodeStrategy.swift
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// EncodeStrategy.swift
|
||||
// CodableFirebase
|
||||
//
|
||||
// Created by Zitao Xiong on 11/3/18.
|
||||
// Copyright © 2018 ViolentOctopus. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The strategy to use for encoding `Date` values.
|
||||
public enum DateEncodingStrategy {
|
||||
/// Defer to `Date` for choosing an encoding. This is the default strategy.
|
||||
case deferredToDate
|
||||
|
||||
case deferredToTimestamp((Date) -> TimestampType)
|
||||
|
||||
/// Encode the `Date` as a UNIX timestamp (as a JSON number).
|
||||
case secondsSince1970
|
||||
|
||||
/// Encode the `Date` as UNIX millisecond timestamp (as a JSON number).
|
||||
case millisecondsSince1970
|
||||
|
||||
/// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
|
||||
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
||||
case iso8601
|
||||
|
||||
/// Encode the `Date` as a string formatted by the given formatter.
|
||||
case formatted(DateFormatter)
|
||||
|
||||
/// Encode the `Date` as a custom value encoded by the given closure.
|
||||
///
|
||||
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
|
||||
case custom((Date, Encoder) throws -> Void)
|
||||
}
|
||||
|
||||
/// The strategy to use for encoding `Data` values.
|
||||
public enum DataEncodingStrategy {
|
||||
/// Defer to `Data` for choosing an encoding.
|
||||
case deferredToData
|
||||
|
||||
/// Encoded the `Data` as a Base64-encoded string. This is the default strategy.
|
||||
case base64
|
||||
|
||||
/// Encode the `Data` as a custom value encoded by the given closure.
|
||||
///
|
||||
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
|
||||
case custom((Data, Encoder) throws -> Void)
|
||||
}
|
||||
|
||||
public enum FirestoreTypeEncodingStrategy {
|
||||
case deferredToPtotocol
|
||||
case custom((_ value: Any) throws -> Any)
|
||||
}
|
||||
|
||||
extension CodingUserInfoKey {
|
||||
public static let dateEncodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "dateEncodingStrategy")!
|
||||
|
||||
public static let dataEncodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "dataEncodingStrategy")!
|
||||
|
||||
public static let skipFirestoreTypes: CodingUserInfoKey = CodingUserInfoKey(rawValue: "skipFirestoreTypes")!
|
||||
|
||||
public static let firestoreTypeEncodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "firestoreTypeEncodingStrategy")!
|
||||
}
|
||||
|
||||
extension Dictionary where Key == CodingUserInfoKey, Value == Any {
|
||||
var dateEncodingStrategy: DateEncodingStrategy? {
|
||||
return self[.dateEncodingStrategy] as? DateEncodingStrategy
|
||||
}
|
||||
|
||||
var dataEncodingStrategy: DataEncodingStrategy? {
|
||||
return self[.dataEncodingStrategy] as? DataEncodingStrategy
|
||||
}
|
||||
|
||||
var skipFirestoreTypes: Bool {
|
||||
if let skip = self[.skipFirestoreTypes] as? Bool {
|
||||
return skip
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var firestoreTypeEncodingStrategy: FirestoreTypeEncodingStrategy {
|
||||
if let strategy = self[.firestoreTypeEncodingStrategy] as? FirestoreTypeEncodingStrategy {
|
||||
return strategy
|
||||
}
|
||||
|
||||
return FirestoreTypeEncodingStrategy.deferredToPtotocol
|
||||
}
|
||||
}
|
||||
@@ -10,25 +10,16 @@ import Foundation
|
||||
|
||||
class _FirebaseEncoder : Encoder {
|
||||
/// Options set on the top-level encoder to pass down the encoding hierarchy.
|
||||
struct _Options {
|
||||
let dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy?
|
||||
let dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy?
|
||||
let skipFirestoreTypes: Bool
|
||||
let userInfo: [CodingUserInfoKey : Any]
|
||||
}
|
||||
|
||||
fileprivate var storage: _FirebaseEncodingStorage
|
||||
fileprivate let options: _Options
|
||||
|
||||
fileprivate(set) public var codingPath: [CodingKey]
|
||||
|
||||
public var userInfo: [CodingUserInfoKey : Any] {
|
||||
return options.userInfo
|
||||
}
|
||||
let userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
init(options: _Options, codingPath: [CodingKey] = []) {
|
||||
init(userInfo: [CodingUserInfoKey: Any], codingPath: [CodingKey] = []) {
|
||||
self.storage = _FirebaseEncodingStorage()
|
||||
self.codingPath = codingPath
|
||||
self.options = options
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
/// Returns whether a new element can be encoded at this coding path.
|
||||
@@ -312,9 +303,11 @@ extension _FirebaseEncoder {
|
||||
}
|
||||
|
||||
fileprivate func box(_ date: Date) throws -> NSObject {
|
||||
guard let options = options.dateEncodingStrategy else { return date as NSDate }
|
||||
guard let options = userInfo.dateEncodingStrategy else { return date as NSDate }
|
||||
|
||||
switch options {
|
||||
case .deferredToTimestamp(let converter):
|
||||
return converter(date) as! NSObject
|
||||
case .deferredToDate:
|
||||
// Must be called with a surrounding with(pushedKey:) call.
|
||||
try date.encode(to: self)
|
||||
@@ -351,7 +344,7 @@ extension _FirebaseEncoder {
|
||||
}
|
||||
|
||||
fileprivate func box(_ data: Data) throws -> NSObject {
|
||||
guard let options = options.dataEncodingStrategy else { return data as NSData }
|
||||
guard let options = userInfo.dataEncodingStrategy else { return data as NSData }
|
||||
|
||||
switch options {
|
||||
case .deferredToData:
|
||||
@@ -385,11 +378,19 @@ extension _FirebaseEncoder {
|
||||
return self.box((value as! URL).absoluteString)
|
||||
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
|
||||
return (value as! NSDecimalNumber)
|
||||
} else if options.skipFirestoreTypes && (value is FirestoreEncodable) {
|
||||
guard let value = value as? NSObject else {
|
||||
} else if userInfo.skipFirestoreTypes && (value is FirestoreEncodable) {
|
||||
let target: Any
|
||||
switch userInfo.firestoreTypeDecodingStrategy {
|
||||
case .deferredToPtotocol:
|
||||
target = value
|
||||
case .custom(let encodeFunc):
|
||||
target = try encodeFunc(value)
|
||||
}
|
||||
|
||||
guard let result = target as? NSObject else {
|
||||
throw DocumentReferenceError.typeIsNotNSObject
|
||||
}
|
||||
return value
|
||||
return result
|
||||
}
|
||||
|
||||
// The value should request a container from the _FirebaseEncoder.
|
||||
@@ -524,7 +525,7 @@ fileprivate class _FirebaseReferencingEncoder : _FirebaseEncoder {
|
||||
fileprivate init(referencing encoder: _FirebaseEncoder, at index: Int, wrapping array: NSMutableArray) {
|
||||
self.encoder = encoder
|
||||
self.reference = .array(array, index)
|
||||
super.init(options: encoder.options, codingPath: encoder.codingPath)
|
||||
super.init(userInfo: encoder.userInfo, codingPath: encoder.codingPath)
|
||||
|
||||
self.codingPath.append(_FirebaseKey(index: index))
|
||||
}
|
||||
@@ -533,7 +534,7 @@ fileprivate class _FirebaseReferencingEncoder : _FirebaseEncoder {
|
||||
fileprivate init(referencing encoder: _FirebaseEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) {
|
||||
self.encoder = encoder
|
||||
reference = .dictionary(dictionary, key.stringValue)
|
||||
super.init(options: encoder.options, codingPath: encoder.codingPath)
|
||||
super.init(userInfo: encoder.userInfo, codingPath: encoder.codingPath)
|
||||
codingPath.append(key)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,54 +9,36 @@
|
||||
import Foundation
|
||||
|
||||
open class FirebaseDecoder {
|
||||
/// The strategy to use for decoding `Date` values.
|
||||
public enum DateDecodingStrategy {
|
||||
/// Defer to `Date` for decoding. This is the default strategy.
|
||||
case deferredToDate
|
||||
|
||||
/// Decode the `Date` as a UNIX timestamp from a JSON number.
|
||||
case secondsSince1970
|
||||
|
||||
/// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
|
||||
case millisecondsSince1970
|
||||
|
||||
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
|
||||
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
||||
case iso8601
|
||||
|
||||
/// Decode the `Date` as a string parsed by the given formatter.
|
||||
case formatted(DateFormatter)
|
||||
|
||||
/// Decode the `Date` as a custom value decoded by the given closure.
|
||||
case custom((_ decoder: Decoder) throws -> Date)
|
||||
}
|
||||
|
||||
/// The strategy to use for decoding `Data` values.
|
||||
public enum DataDecodingStrategy {
|
||||
/// Defer to `Data` for decoding.
|
||||
case deferredToData
|
||||
|
||||
/// Decode the `Data` from a Base64-encoded string. This is the default strategy.
|
||||
case base64
|
||||
|
||||
/// Decode the `Data` as a custom value decoded by the given closure.
|
||||
case custom((_ decoder: Decoder) throws -> Data)
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
open var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
open var dateDecodingStrategy: DateDecodingStrategy = .deferredToDate
|
||||
open var dataDecodingStrategy: DataDecodingStrategy = .deferredToData
|
||||
|
||||
open var userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
|
||||
public var dateDecodingStrategy: DateDecodingStrategy {
|
||||
set {
|
||||
userInfo[.dateDecodingStrategy] = newValue
|
||||
}
|
||||
get {
|
||||
if let strategy = userInfo[.dateDecodingStrategy] as? DateDecodingStrategy {
|
||||
return strategy
|
||||
}
|
||||
return .deferredToDate
|
||||
}
|
||||
}
|
||||
|
||||
public var dataDecodingStrategy: DataDecodingStrategy {
|
||||
set {
|
||||
userInfo[.dataDecodingStrategy] = newValue
|
||||
}
|
||||
get {
|
||||
if let strategy = userInfo[.dataDecodingStrategy] as? DataDecodingStrategy {
|
||||
return strategy
|
||||
}
|
||||
return .deferredToData
|
||||
}
|
||||
}
|
||||
|
||||
open func decode<T : Decodable>(_ type: T.Type, from container: Any) throws -> T {
|
||||
let options = _FirebaseDecoder._Options(
|
||||
dateDecodingStrategy: dateDecodingStrategy,
|
||||
dataDecodingStrategy: dataDecodingStrategy,
|
||||
skipFirestoreTypes: false,
|
||||
userInfo: userInfo
|
||||
)
|
||||
let decoder = _FirebaseDecoder(referencing: container, options: options)
|
||||
let decoder = _FirebaseDecoder(referencing: container, userInfo: userInfo)
|
||||
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"))
|
||||
}
|
||||
|
||||
@@ -9,58 +9,12 @@
|
||||
import Foundation
|
||||
|
||||
open class FirebaseEncoder {
|
||||
/// The strategy to use for encoding `Date` values.
|
||||
public enum DateEncodingStrategy {
|
||||
/// Defer to `Date` for choosing an encoding. This is the default strategy.
|
||||
case deferredToDate
|
||||
|
||||
/// Encode the `Date` as a UNIX timestamp (as a JSON number).
|
||||
case secondsSince1970
|
||||
|
||||
/// Encode the `Date` as UNIX millisecond timestamp (as a JSON number).
|
||||
case millisecondsSince1970
|
||||
|
||||
/// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
|
||||
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
||||
case iso8601
|
||||
|
||||
/// Encode the `Date` as a string formatted by the given formatter.
|
||||
case formatted(DateFormatter)
|
||||
|
||||
/// Encode the `Date` as a custom value encoded by the given closure.
|
||||
///
|
||||
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
|
||||
case custom((Date, Encoder) throws -> Void)
|
||||
}
|
||||
|
||||
/// The strategy to use for encoding `Data` values.
|
||||
public enum DataEncodingStrategy {
|
||||
/// Defer to `Data` for choosing an encoding.
|
||||
case deferredToData
|
||||
|
||||
/// Encoded the `Data` as a Base64-encoded string. This is the default strategy.
|
||||
case base64
|
||||
|
||||
/// Encode the `Data` as a custom value encoded by the given closure.
|
||||
///
|
||||
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
|
||||
case custom((Data, Encoder) throws -> Void)
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
open var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate
|
||||
open var dataEncodingStrategy: DataEncodingStrategy = .deferredToData
|
||||
|
||||
|
||||
open func encode<Value : Encodable>(_ value: Value) throws -> Any {
|
||||
let options = _FirebaseEncoder._Options(
|
||||
dateEncodingStrategy: dateEncodingStrategy,
|
||||
dataEncodingStrategy: dataEncodingStrategy,
|
||||
skipFirestoreTypes: false,
|
||||
userInfo: userInfo
|
||||
)
|
||||
let encoder = _FirebaseEncoder(options: options)
|
||||
let encoder = _FirebaseEncoder(userInfo: userInfo)
|
||||
guard let topLevel = try encoder.box_(value) else {
|
||||
throw EncodingError.invalidValue(value,
|
||||
EncodingError.Context(codingPath: [],
|
||||
|
||||
@@ -26,18 +26,15 @@ public protocol TimestampType: FirestoreDecodable, FirestoreEncodable {
|
||||
}
|
||||
|
||||
open class FirestoreDecoder {
|
||||
public init() {}
|
||||
public init(userInfo: [CodingUserInfoKey: Any] = [.skipFirestoreTypes: true]) {
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
open var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
public let 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,
|
||||
skipFirestoreTypes: true,
|
||||
userInfo: userInfo
|
||||
)
|
||||
let decoder = _FirebaseDecoder(referencing: container, options: options)
|
||||
let decoder = _FirebaseDecoder(referencing: container,
|
||||
userInfo: userInfo)
|
||||
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"))
|
||||
}
|
||||
@@ -87,7 +84,7 @@ extension TimestampType {
|
||||
let container = try decoder.singleValueContainer()
|
||||
self.init(date: try container.decode(Date.self))
|
||||
}
|
||||
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(self.dateValue())
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
import Foundation
|
||||
|
||||
open class FirestoreEncoder {
|
||||
public init() {}
|
||||
|
||||
open var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
public init(userInfo: [CodingUserInfoKey: Any] = [.skipFirestoreTypes: true]) {
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
public let userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
open func encode<Value : Encodable>(_ value: Value) throws -> [String: Any] {
|
||||
let topLevel = try encodeToTopLevelContainer(value)
|
||||
switch topLevel {
|
||||
@@ -26,13 +28,7 @@ open class FirestoreEncoder {
|
||||
}
|
||||
|
||||
internal func encodeToTopLevelContainer<Value : Encodable>(_ value: Value) throws -> Any {
|
||||
let options = _FirebaseEncoder._Options(
|
||||
dateEncodingStrategy: nil,
|
||||
dataEncodingStrategy: nil,
|
||||
skipFirestoreTypes: true,
|
||||
userInfo: userInfo
|
||||
)
|
||||
let encoder = _FirebaseEncoder(options: options)
|
||||
let encoder = _FirebaseEncoder(userInfo: userInfo)
|
||||
guard let topLevel = try encoder.box_(value) else {
|
||||
throw EncodingError.invalidValue(value,
|
||||
EncodingError.Context(codingPath: [],
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2.0</string>
|
||||
<string>0.2.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2.0</string>
|
||||
<string>0.2.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
|
||||
@@ -392,15 +392,15 @@ class TestCodableFirebase: XCTestCase {
|
||||
|
||||
private func _testRoundTrip<T>(of value: T,
|
||||
expectedValue json: Any? = nil,
|
||||
dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy = .deferredToDate,
|
||||
dateDecodingStrategy: FirebaseDecoder.DateDecodingStrategy = .deferredToDate,
|
||||
dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy = .base64,
|
||||
dataDecodingStrategy: FirebaseDecoder.DataDecodingStrategy = .base64) where T : Codable, T : Equatable {
|
||||
dateEncodingStrategy: DateEncodingStrategy = .deferredToDate,
|
||||
dateDecodingStrategy: DateDecodingStrategy = .deferredToDate,
|
||||
dataEncodingStrategy: DataEncodingStrategy = .base64,
|
||||
dataDecodingStrategy: DataDecodingStrategy = .base64) where T : Codable, T : Equatable {
|
||||
var payload: Any! = nil
|
||||
do {
|
||||
let encoder = FirebaseEncoder()
|
||||
encoder.dateEncodingStrategy = dateEncodingStrategy
|
||||
encoder.dataEncodingStrategy = dataEncodingStrategy
|
||||
encoder.userInfo[.dateEncodingStrategy] = dateEncodingStrategy
|
||||
encoder.userInfo[.dataEncodingStrategy] = dataEncodingStrategy
|
||||
payload = try encoder.encode(value)
|
||||
} catch {
|
||||
XCTFail("Failed to encode \(T.self) to val: \(error)")
|
||||
|
||||
@@ -128,14 +128,36 @@ class TestCodableFirestore: XCTestCase {
|
||||
XCTAssertEqual((try? FirestoreEncoder().encode(val)) as NSDictionary?, ["value": val.value])
|
||||
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<DocumentReference>.self, from: ["value": val.value]), val)
|
||||
}
|
||||
|
||||
|
||||
func testEncodingTimestamp() {
|
||||
let timestamp = Timestamp(date: Date())
|
||||
let wrapper = TopLevelWrapper(timestamp)
|
||||
XCTAssertEqual((try? FirestoreEncoder().encode(wrapper)) as NSDictionary?, ["value": timestamp])
|
||||
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<Timestamp>.self, from: ["value": timestamp]), wrapper)
|
||||
}
|
||||
|
||||
|
||||
func testCustomEncodingTimestamp() {
|
||||
let date = Date()
|
||||
let timestamp = Timestamp(date: date)
|
||||
|
||||
// encode date to Timestamp
|
||||
let encodeWrapper = TopLevelWrapper(date)
|
||||
let encodeResult = (try? FirestoreEncoder(userInfo: [CodingUserInfoKey.dateEncodingStrategy: DateEncodingStrategy.deferredToTimestamp({ date in
|
||||
return Timestamp(date: date)
|
||||
})])
|
||||
.encode(encodeWrapper)) as NSDictionary?
|
||||
XCTAssertEqual(encodeResult, ["value": timestamp])
|
||||
|
||||
// decode timestamp to date
|
||||
let decodeWrapper = TopLevelWrapper(timestamp)
|
||||
let decoder = FirestoreDecoder(userInfo: [
|
||||
CodingUserInfoKey.dateDecodingStrategy: DateDecodingStrategy.deferredToTimestamp
|
||||
])
|
||||
|
||||
let decodeResult = (try? decoder.decode(TopLevelWrapper<Timestamp>.self, from: ["value": timestamp]))
|
||||
XCTAssertEqual(decodeResult, decodeWrapper)
|
||||
}
|
||||
|
||||
private func _testEncodeFailure<T : Encodable>(of value: T) {
|
||||
do {
|
||||
let _ = try FirestoreEncoder().encode(value)
|
||||
@@ -214,15 +236,15 @@ fileprivate class DocumentReference: NSObject, DocumentReferenceType {}
|
||||
// MARK: - Timestamp
|
||||
fileprivate class Timestamp: NSObject, TimestampType {
|
||||
let date: Date
|
||||
|
||||
|
||||
required init(date: Date) {
|
||||
self.date = date
|
||||
}
|
||||
|
||||
|
||||
func dateValue() -> Date {
|
||||
return date
|
||||
}
|
||||
|
||||
|
||||
override func isEqual(_ object: Any?) -> Bool {
|
||||
guard let other = object.flatMap({ $0 as? Timestamp }) else { return false }
|
||||
return date == other.date
|
||||
|
||||
28
README.md
28
README.md
@@ -20,13 +20,13 @@ struct Model: Codable {
|
||||
let numberExample: Double
|
||||
let dateExample: Date
|
||||
let arrayExample: [String]
|
||||
let nullExample: Int?
|
||||
let optionalExample: Int?
|
||||
let objectExample: [String: String]
|
||||
let myEnum: MyEnum
|
||||
let myEnumExample: MyEnum
|
||||
}
|
||||
```
|
||||
|
||||
### Firebase Database usage
|
||||
### Firebase Realtime Database usage
|
||||
|
||||
This is how you would use the library with [Firebase Realtime Database](https://firebase.google.com/products/realtime-database/):
|
||||
|
||||
@@ -43,7 +43,7 @@ Database.database().reference().child("model").setValue(data)
|
||||
And here is how you would read the same value from [Firebase Realtime Database](https://firebase.google.com/products/realtime-database/):
|
||||
|
||||
```swift
|
||||
Database.database().reference().child("model").observeSingleEvent(of: .value, with: { (snapshot) in
|
||||
Database.database().reference().child("model").observeSingleEvent(of: .value, with: { snapshot in
|
||||
guard let value = snapshot.value else { return }
|
||||
do {
|
||||
let model = try FirebaseDecoder().decode(Model.self, from: value)
|
||||
@@ -54,9 +54,9 @@ Database.database().reference().child("model").observeSingleEvent(of: .value, wi
|
||||
})
|
||||
```
|
||||
|
||||
### Firestore usage
|
||||
### Firebase Cloud Firestore usage
|
||||
|
||||
And this is how you would encode it with [Firebase Firestore](https://firebase.google.com/products/firestore/):
|
||||
This is how you would encode a model with [Firebase Cloud Firestore](https://firebase.google.com/products/firestore/):
|
||||
|
||||
```swift
|
||||
import Firebase
|
||||
@@ -64,19 +64,19 @@ import CodableFirebase
|
||||
|
||||
let model: Model // here you will create an instance of Model
|
||||
let docData = try! FirestoreEncoder().encode(model)
|
||||
Firestore.firestore().collection("data").document("one").setData(docData) { err in
|
||||
if let err = err {
|
||||
print("Error writing document: \(err)")
|
||||
Firestore.firestore().collection("data").document("one").setData(docData) { error in
|
||||
if let error = error {
|
||||
print("Error writing document: \(error)")
|
||||
} else {
|
||||
print("Document successfully written!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And this is how you would decode the same model with [Firebase Firestore](https://firebase.google.com/products/firestore/):
|
||||
And this is how you would decode the same model with [Firebase Cloud Firestore](https://firebase.google.com/products/firestore/):
|
||||
|
||||
```swift
|
||||
Firestore.firestore().collection("data").document("one").getDocument { (document, error) in
|
||||
Firestore.firestore().collection("data").document("one").getDocument { document, error in
|
||||
if let document = document {
|
||||
let model = try! FirestoreDecoder().decode(Model.self, from: document.data())
|
||||
print("Model: \(model)")
|
||||
@@ -86,9 +86,9 @@ Firestore.firestore().collection("data").document("one").getDocument { (document
|
||||
}
|
||||
```
|
||||
|
||||
### How to use `GeoPoint`, `DocumentRefence`, `FieldValue`, `Timestamp` in Firestore
|
||||
#### How to use `GeoPoint`, `DocumentRefence`, `FieldValue`, `Timestamp` in Cloud Firestore
|
||||
|
||||
In order to use these 2 types with `Firestore`, you need to add the following code somewhere in your app:
|
||||
In order to use these types with Cloud Firestore, you need to add the following code somewhere in your app:
|
||||
|
||||
```swift
|
||||
extension DocumentReference: DocumentReferenceType {}
|
||||
@@ -112,7 +112,7 @@ platform :ios, '9.0'
|
||||
use_frameworks!
|
||||
|
||||
target 'MyApp' do
|
||||
pod 'CodableFirebase'
|
||||
pod 'CodableFirebase'
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ platform :ios do
|
||||
end
|
||||
|
||||
def release(type)
|
||||
pod_lib_lint
|
||||
pod_lib_lint(
|
||||
allow_warnings: true,
|
||||
use_libraries: true,
|
||||
)
|
||||
podspec_name = "CodableFirebase.podspec"
|
||||
version = version_bump_podspec(path: podspec_name,
|
||||
bump_type: type)
|
||||
@@ -29,6 +32,9 @@ platform :ios do
|
||||
message: "#{version} release")
|
||||
add_git_tag(tag: "#{version}")
|
||||
push_to_git_remote
|
||||
pod_push
|
||||
pod_push(
|
||||
allow_warnings: true,
|
||||
use_libraries: true,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user