Skip to content
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

Codable records: customize the userInfo context dictionary, and the format of JSON columns #399

Merged
merged 24 commits into from
Aug 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
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