Replies: 2 comments 1 reply
-
Hello @mickeyl, You can indeed customize the JSONEncoder and decoder of a given column with the There is no built-in support for any encoder, or top-level encoder. This would be handy indeed! Meanwhile, you can provide a custom implementation for |
Beta Was this translation helpful? Give feedback.
-
@mickeyl, I have looked at this a little more, and there are multiple obstacles on the way to replace the hard-coded dependency on Most importantly, I'm comfortable with providing convenience apis for the JSON format, since JSON is very popular, very useful, and SQLite has great built-in support for JSON. We can expect GRDB support for JSON to improve in the future. Now, I want to remind that you have several escape hatches. When the built-in convenience is not what you need, you can still rely on the versatility of the library. I already mentioned above that you can opt out of the default support for The other solution I want to share now relies on the documented GRDB preference for In the sample code below, I define a /// A property wrapper that encodes and decodes its wrapped value as
/// property-list (only in the database).
@propertyWrapper
struct PropertyListDatabaseCoding<Value: Codable> {
var wrappedValue: Value
}
// Outside of the database context, PropertyListDatabaseCoding is neutral w.r.t.
// Codable: it encodes and decodes just as its wrappedValue.
extension PropertyListDatabaseCoding: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
/// CustomDatabaseCoding performs property-list coding in the database
extension PropertyListDatabaseCoding: DatabaseValueConvertible {
var databaseValue: DatabaseValue {
// We'll have to wait for GRDB6 in order to deal with types that support
// encoding errors. Until then, all DatabaseValueConvertible types must
// not fail.
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
return try! encoder.encode(wrappedValue).databaseValue
}
static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
guard let data = Data.fromDatabaseValue(dbValue) else { return nil }
let decoder = PropertyListDecoder()
return try? PropertyListDatabaseCoding(wrappedValue: decoder.decode(Value.self, from: data))
}
}
/// Optional, but recommended: CustomDatabaseCoding optimizes database decoding
/// and memory consumption whenever we have access to low-level SQLite apis.
extension PropertyListDatabaseCoding: StatementColumnConvertible {
init?(sqliteStatement: SQLiteStatement, index: Int32) {
let data: Data
if let bytes = sqlite3_column_blob(sqliteStatement, index) {
let count = Int(sqlite3_column_bytes(sqliteStatement, index))
data = Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes), count: count, deallocator: .none)
} else {
data = Data()
}
let decoder = PropertyListDecoder()
if let value = try? decoder.decode(Value.self, from: data) {
self = Self(wrappedValue: value)
} else {
return nil
}
}
} Usage: struct Player: Codable, FetchableRecord, PersistableRecord {
var id: Int64
var name: String
@PropertyListDatabaseCoding var medals: [String]
}
let dbQueue = DatabaseQueue()
try dbQueue.write { db in
try db.create(table: "player") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("medals", .blob).notNull()
}
// Encoding
let player = Player(id: 1, name: "Arthur", medals: ["gold", "silver"])
try player.insert(db)
// Control the stored property-list blob
let medals = try String.fetchOne(db, sql: "SELECT medals FROM player")!
// <?xml version="1.0" encoding="UTF-8"?>
// <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
// <plist version="1.0">
// <array>
// <string>gold</string>
// <string>silver</string>
// </array>
// </plist>
print(medals)
// Decoding
if let player = try Player.fetchOne(db) {
print(player.medals) // ["gold", "silver"]
}
// Check non-database encoding
let json = try String(data: JSONEncoder().encode(player), encoding: .utf8)!
print(json) // {"id":1,"name":"Arthur","medals":["gold","silver"]}
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I'm pretty much satisfied with the default behavior of GRDB, however I'd like to replace the default JSON coder w/ another one, e.g., https://github.com/hirotakan/MessagePacker, as encoder + decoder for all values.
I learned about
DatabaseValueConvertible
, but is this the right way also for "top-level" items?Update: I just learned about
static func databaseJSONEncoder
which looks like a better approach, but it requires aJSONEncoder
, not any Encoder.Beta Was this translation helpful? Give feedback.
All reactions