SQLite.swift中Codable功能与Int枚举兼容问题求助
这个问题我之前也碰到过,根源是SQLite.swift对Codable枚举的默认处理逻辑和你的需求不匹配——它会把枚举当成嵌套Codable类型编码成JSON片段,但你的数据库列是Int类型,所以才会抛出Top-level MyEnum encoded as number JSON fragment的错误。下面给你两种优雅的解决办法,都能避免回到手动写序列化/反序列化的老代码:
方案1:让枚举适配SQLite.swift的Value协议(推荐)
SQLite.swift的Value协议定义了Swift类型和数据库列类型之间的转换规则。对于你的Int-backed枚举,只需要简单实现这个协议,就能让SQLite.swift自动处理枚举和数据库Int列的映射,完全不用修改Codable逻辑。
首先修改你的枚举:
enum MyEnum: Int, Codable, Value { case FIRSTVAL = 0, SECONDVAL = 1 // 实现Value协议的三个要求 static var declaredDatatype: String { // 复用Int的数据库类型定义 return Int.declaredDatatype } static func fromDatatypeValue(_ datatypeValue: Int) -> MyEnum { // 从数据库的Int值转成枚举 return MyEnum(rawValue: datatypeValue)! } var datatypeValue: Int { // 把枚举转成对应的Int rawValue存入数据库 return rawValue } }
然后更新MyClass里的列定义,把Expression<Int>改成Expression<MyEnum>:
class MyClass : Codable { static let tableRef = Table("MyTable") static let idColumn = Expression<Int>("MyTableId"); static let nameColumn = Expression<String>("Name") // 改成Expression<MyEnum> static let myEnumValColumn = Expression<MyEnum>("MyEnumVal") // 其他代码不变... var MyTableId : Int! var Name : String var MyEnumVal : MyEnum }
这样修改后,你原来的插入代码try db.run(MyClass.tableRef.insert(anInstanceOfMyClass))就能直接正常工作了。SQLite.swift会自动把枚举转换成对应的Int值存入数据库,查询时也会自动把Int值转回枚举类型,全程不需要手动干预。
方案2:自定义MyClass的Codable实现
如果你不想修改枚举的定义,也可以在MyClass里手动实现Codable的编码和解码方法,强制把枚举转换成rawValue处理:
class MyClass : Codable { static let tableRef = Table("MyTable") static let idColumn = Expression<Int>("MyTableId"); static let nameColumn = Expression<String>("Name") static let myEnumValColumn = Expression<Int>("MyEnumVal") static func createTable(_ db : Connection) throws { try db.run(tableRef.create(ifNotExists: true) { t in t.column(idColumn, primaryKey: true) t.column(nameColumn) t.column(myEnumValColumn) }) } var MyTableId : Int! var Name : String var MyEnumVal : MyEnum // 定义CodingKeys,对应数据库列名 enum CodingKeys: String, CodingKey { case MyTableId, Name, MyEnumVal } // 自定义编码逻辑:把枚举转成rawValue func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(MyTableId, forKey: .MyTableId) try container.encode(Name, forKey: .Name) try container.encode(MyEnumVal.rawValue, forKey: .MyEnumVal) } // 自定义解码逻辑:从rawValue转成枚举 required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) MyTableId = try container.decode(Int.self, forKey: .MyTableId) Name = try container.decode(String.self, forKey: .Name) let rawValue = try container.decode(Int.self, forKey: .MyEnumVal) guard let enumValue = MyEnum(rawValue: rawValue) else { throw DecodingError.dataCorruptedError( forKey: .MyEnumVal, in: container, debugDescription: "无效的MyEnum raw值: \(rawValue)" ) } MyEnumVal = enumValue } }
这种方式下,SQLite.swift会使用你自定义的Codable逻辑,把枚举的rawValue存入Int列,同样不需要回到你之前写的手动insert和init(row)方法。
方案对比
- 方案1更优雅,一次实现后,所有使用这个枚举的SQLite.swift列都能自动适配,代码复用性更高。
- 方案2适合你无法修改枚举定义的场景,但需要在每个包含该枚举的Codable类中重复编写编码解码逻辑。
内容的提问来源于stack exchange,提问作者Neil Mountford

