Skip to content

Commit

Permalink
Merge pull request #399 from groue/feature/codableUserInfo
Browse files Browse the repository at this point in the history
Codable support: customize the `userInfo` context dictionary, and the format of JSON columns
  • Loading branch information
groue authored Aug 19, 2018
2 parents 2bc5625 + 1e25deb commit 1fba575
Show file tree
Hide file tree
Showing 11 changed files with 1,322 additions and 614 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Release Notes
### New

- [#397](https://github.com/groue/GRDB.swift/pull/397): JSON encoding and decoding of codable record properties
- [#399](https://github.com/groue/GRDB.swift/pull/399): Codable support: customize the `userInfo` context dictionary, and the format of JSON columns
- [#393](https://github.com/groue/GRDB.swift/pull/393): Upgrade SQLCipher to 3.4.2, enable FTS5 on GRDBCipher and new pod GRDBPlus.
- [#384](https://github.com/groue/GRDB.swift/pull/384): Improve database value decoding diagnostics
- Cursors of optimized values (Strint, Int, Date, etc.) have been renamed: FastDatabaseValueCursor and FastNullableDatabaseValueCursor replace the deprecated ColumnCursor and NullableColumnCursor.
Expand All @@ -26,6 +27,16 @@ Release Notes
+ func unsafeReentrantRead<T>(_ block: (Database) throws -> T) rethrows -> T {
}

protocol FetchableRecord {
+ static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] { get }
+ static func databaseJSONDecoder(for column: String) -> JSONDecoder
}

protocol MutablePersistableRecord: TableRecord {
+ static var databaseEncodingUserInfo: [CodingUserInfoKey: Any] { get }
+ static func databaseJSONEncoder(for column: String) -> JSONEncoder
}

+final class FastDatabaseValueCursor<Value: DatabaseValueConvertible & StatementColumnConvertible> : Cursor { }
+@available(*, deprecated, renamed: "FastDatabaseValueCursor")
+typealias ColumnCursor<Value: DatabaseValueConvertible & StatementColumnConvertible> = FastDatabaseValueCursor<Value>
Expand All @@ -39,7 +50,8 @@ Release Notes
### Documentation Diff

- [Enabling FTS5 Support](README.md#enabling-fts5-support): Procedure for enabling FTS5 support in GRDB.
- [Codable Records](README.md#codable-records): Updated documentation, for JSON encoding of codable record properties, and for the reuse of coding keys as database columns.
- [Codable Records](README.md#codable-records): Updated documentation for JSON columns, tips, and customization options.
- [Record Customization Options](README.md#record-customization-options): A new chapter that gather all your customization options.


## 3.2.0
Expand Down
465 changes: 208 additions & 257 deletions GRDB/Record/FetchableRecord+Decodable.swift

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions GRDB/Record/FetchableRecord.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Foundation
#if SWIFT_PACKAGE
import CSQLite
#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
Expand All @@ -24,12 +25,95 @@
/// FetchableRecord is adopted by Record.
public protocol FetchableRecord {

// MARK: - Row Decoding

/// Creates a record from `row`.
///
/// For performance reasons, the row argument may be reused during the
/// iteration of a fetch query. If you want to keep the row for later use,
/// make sure to store a copy: `self.row = row.copy()`.
init(row: Row)

// MARK: - Customizing the Format of Database Columns

/// When the FetchableRecord type also adopts the standard Decodable
/// protocol, you can use this dictionary to customize the decoding process
/// from database rows.
///
/// For example:
///
/// // A key that holds a decoder's name
/// let decoderName = CodingUserInfoKey(rawValue: "decoderName")!
///
/// // A FetchableRecord + Decodable record
/// struct Player: FetchableRecord, Decodable {
/// // Customize the decoder name when decoding a database row
/// static let databaseDecodingUserInfo: [CodingUserInfoKey: Any] = [decoderName: "Database"]
///
/// init(from decoder: Decoder) throws {
/// // Print the decoder name
/// print(decoder.userInfo[decoderName])
/// ...
/// }
/// }
///
/// // prints "Database"
/// let player = try Player.fetchOne(db, ...)
///
/// // prints "JSON"
/// let decoder = JSONDecoder()
/// decoder.userInfo = [decoderName: "JSON"]
/// let player = try decoder.decode(Player.self, from: ...)
static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] { get }

/// When the FetchableRecord type also adopts the standard Decodable
/// protocol, this method controls the decoding process of nested properties
/// from JSON database columns.
///
/// The default implementation returns a JSONDecoder with the
/// following properties:
///
/// - dataDecodingStrategy: .base64
/// - dateDecodingStrategy: .millisecondsSince1970
/// - nonConformingFloatDecodingStrategy: .throw
///
/// You can override those defaults:
///
/// struct Achievement: Decodable {
/// var name: String
/// var date: Date
/// }
///
/// struct Player: Decodable, FetchableRecord {
/// // stored in a JSON column
/// var achievements: [Achievement]
///
/// static func databaseJSONDecoder(for column: String) -> JSONDecoder {
/// let decoder = JSONDecoder()
/// decoder.dateDecodingStrategy = .iso8601
/// return decoder
/// }
/// }
static func databaseJSONDecoder(for column: String) -> JSONDecoder
}

extension FetchableRecord {
public static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] {
return [:]
}

/// Returns a JSONDecoder with the following properties:
///
/// - dataDecodingStrategy: .base64
/// - dateDecodingStrategy: .millisecondsSince1970
/// - nonConformingFloatDecodingStrategy: .throw
public static func databaseJSONDecoder(for column: String) -> JSONDecoder {
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64
decoder.dateDecodingStrategy = .millisecondsSince1970
decoder.nonConformingFloatDecodingStrategy = .throw
return decoder
}
}

/// A cursor of records. For example:
Expand Down
Loading

0 comments on commit 1fba575

Please sign in to comment.