sql-kit icon indicating copy to clipboard operation
sql-kit copied to clipboard

JSON columns with Date and/or Bool fail decoding

Open janigro opened this issue 2 years ago • 2 comments

Problem Description

When trying to decode a query that has a JSON column with booleans and/or date values, it fails with an error. Consider the following minimal example:

struct MyContent: Content {
    let options: Options
    
    struct Options: Codable {
        let isEnabled: Bool
        let due: Date
    }
}

let query: SQLQueryString = "SELECT JSON_OBJECT('isEnabled', 1, 'due', NOW()) options"

let results = try await db.raw(query).first(decoding: MyContent.self)

Running this code will produce:

Value of type 'Bool' required for key 'isEnabled'.

if we change the Bool to Int, we now get an error because of the Date value:

"Value of type 'Double' required for key 'due'.

The second error can be omitted by changing the type in the due property, from Date to String

Expected Behavior

Not sure if this is a bug report, or a feature request. But ideally, Fluent should be able to decode Bool and Date types. Or maybe I'm missing something obvious of why this is not the case?

Workaround

In the meantime, I'm implementing Econdable.init(from decoder: Decoder) to workaround the problem:

struct MyContent: Content {
    let options: Options
    
    struct Options: Codable {
        let isEnabled: Bool
        let due: Date
        
        init(from decoder: Decoder) throws {
            let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
            
            self.isEnabled = (try container.decode(Int.self, forKey: CodingKeys.isEnabled) != 0)
            
            let d = try container.decode(String.self, forKey: CodingKeys.due)
            
            let formatter = DateFormatter()
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS"
            formatter.timeZone = TimeZone(secondsFromGMT: 0)
            
            guard let due = formatter.date(from: (try container.decode(String.self, forKey: CodingKeys.due))) else {
                throw DecodingError.typeMismatch(Date.self, DecodingError.Context(codingPath: [CodingKeys.due], debugDescription: "String could not be converted to Date"))
            }
            
            self.due = due
        }
    }
}

This workaround now produces proper JSON content:

{
  "options": {
    "due": "2023-01-13T11:49:59Z",
    "isEnabled": true
  }
}

Environment

mysql server: 8.0.27 framework: 4.67.4 toolbox: 18.6.0

janigro avatar Jan 13 '23 12:01 janigro