Sending Explicit `null` Values to Supabase from Swift
While implementing a grace period for teachers in our Music Theory Learning app, we ran into a subtle issue when updating data in Supabase. Teachers can have an expiration timestamp stored in the database. When a grace period resets, we need to clear this value by setting it to `NULL`.
Our update model looked like this:
struct UpdateDTO: Encodable {
let grace_period_ends_at: Date?
}
Setting the property to `nil` seemed like the obvious solution: `UpdateDTO(grace_period_ends_at: nil)` However, Swift's synthesized `Codable` implementation omits optional properties that are `nil`. Instead of sending `{ "grace_period_ends_at": null }` the generated JSON was: `{ }`. For Supabase, these two payloads have different meanings. The first one means "set the column to NULL" and the second "leave column unchanged".
To force an explicit `null`, we implemented custom encoding in the `UpdateDTO` struct:
struct UpdateDTO: Codable {
var subscription_status: String?
var grace_period_ends_at: String?
enum CodingKeys: String, CodingKey {
case subscription_status
case grace_period_ends_at
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(subscription_status, forKey: .subscription_status)
if let g = grace_period_ends_at {
try container.encode(g, forKey: .grace_period_ends_at)
} else {
try container.encodeNil(forKey: .grace_period_ends_at)
}
}
}
A small detail, but an important one when working with Supabase updates. If you need to clear a database column, `nil` in Swift does not automatically become `null` in JSON.
// To reset the end time:
updateDTO.grace_period_ends_at = nil
// Send the update:
try await supabaseClient
.from("teachers")
.update(updateDTO)
.eq("id", value: teacherId.uuidString)
.execute()
The key takeaway is that Swift's default `Codable` behavior treats `nil` as "omit this field," whereas Supabase distinguishes between an omitted field and a field explicitly set to `null`. When you need to clear a column, custom encoding is required.