Skip to content

[WIP] Refactor and support encoding to timestamp and decoding to date #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions CodableFirebase.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -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>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -222,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;
Expand Down
77 changes: 77 additions & 0 deletions CodableFirebase/DecodeStrategy.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
46 changes: 22 additions & 24 deletions CodableFirebase/Decoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -1109,14 +1096,17 @@ 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)
}
return date
}

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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()
Expand Down
89 changes: 89 additions & 0 deletions CodableFirebase/EncodeStrategy.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading