Supabase Swift does not encode / decode fractional seconds on Date types
Bug report
Supabase Swift does not encode / decode fractional seconds on Date types
Which makes it very problematic for syncing use-cases.
Reason might be the API uses the standard Swift JSONEncoder/Decoder .iso8601 which does not support that. Has to be a custom Encoder/Deocder.
Hi @chrisrohrer do you have a JSON example which breaks the current decoder instance?
Hi @grdsdev sure, here is a test code setup. In the retrieved date fractional seconds are always .000Z
I suppose they are already skipped in the encode at .insert(item)
SQL
CREATE TABLE public."Test" (
id bigint primary key generated always as identity,
date timestamp with time zone
);
SWIFT
struct TestItem: Identifiable, Codable {
var id: Int?
var date: Date
}
func dateCodingTest() {
Task {
// formatter to display fractional seconds
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
do {
let item = TestItem(date: .now)
print(formatter.string(from: item.date), "- set date with fractional secs")
let response = try await supabase
.from("Test")
.insert(item)
.execute()
print(response.status)
let serverRecords: [TestItem] = try await supabase
.from("Test")
.select()
.order("date", ascending: true)
.execute()
.value
let last = serverRecords.last!
print(formatter.string(from: last.date), "- retrieved date without fractional secs")
} catch {
print(#function, error)
}
}
}
I'm getting the same error on a realtime subscription;
channel.onPostgresChange(AnyAction.self,
table: "chat_members",
filter: "user_id=eq.\(me.id)") { action in
do {
switch action {
// ----- INSERT
case .insert(let insert):
print("Was added to chat: \(insert.record)")
let chat = try insert.decodeRecord(as: ChatMember.self, decoder: .supabaseCompatibleDecoder)
self.fetchAndAddChat(id: chat.chat_id)
break
// ...
Error:
- Failed to update chat relationship! dataCorrupted(Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "joined_at", intValue: nil)], debugDescription: "Expected date string to be ISO8601-formatted with fractional seconds", underlyingError: nil))
My `.supabaseCompatibleDecoder`:
struct ISODateFormatter {
let isoFormatterWithFractional = ISO8601DateFormatter()
let isoFormatterWithoutFractional = ISO8601DateFormatter()
init() {
isoFormatterWithFractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
isoFormatterWithoutFractional.formatOptions = [.withInternetDateTime]
}
func date(from string: String) -> Date? {
if let date = isoFormatterWithFractional.date(from: string) {
return date
}
if let date = isoFormatterWithoutFractional.date(from: string) {
return date
}
return nil
}
}
extension JSONDecoder {
static var supabaseCompatibleDecoder: JSONDecoder = {
let formatter = ISODateFormatter()
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom { decoder in
var container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
guard let date = formatter.date(from: dateString) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Expected date string to be ISO8601-formatted with fractional seconds"
)
}
return date
}
return decoder
}()
}
..not sure if I am using the realtime API correctly though, are we always expected to use the .decodeRecord(as: ..., decoder: ...) API?
Nevermind, sorry - I just rolled my own JSONDecoder which didn't work - I now found PostgrestClient.Configuration.jsonDecoder which does the trick.
Maybe this should be added to the realtime docs, or an API should be added to the postgres listener that can just decode with the internal encoder/decoder so people don't write their own? I could look into adding this to the client library.