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.
-
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