处理JSON与Realm数据库时,是否必须维护独立的结构体或类?
我从API请求获取JSON数据时,用以下Codable结构体解码:
import Foundation struct JSON: Codable { var alpha: Alpha var beta: Beta var gamma: [Gamma]? }
现在想把这些数据存入Realm数据库,方便后续操作和遍历。但目前了解到必须重新编写Realm专用类,比如:
import Foundation import RealmSwift class RealmJSON: Object, Identifiable { @Persisted (primaryKey: true) var id: ObjectId @Persisted var alpha: RealmAlpha @Persisted var beta: RealmBeta @Persisted var gamma: RealmSwift.List<RealmGamma>? override class func primaryKey() -> String? { "id" } convenience init(id: ObjectId, alpha: RealmAlpha, beta: RealmBeta, gamma: RealmSwift.List<RealmGamma>?) { self.init() self.id = id self.alpha = alpha self.beta = beta self.gamma = gamma } }
这种方式在处理大量JSON数据时非常不便,而且我想用Swagger Codegen自动生成客户端代码,手动写Realm类会让自动生成失去意义。
请问这是唯一的处理方式吗?有没有其他更高效的方法?
补充想法:我考虑过把大部分JSON数据存为原始字符串,同时添加标识schema类型/版本的属性,之后根据schema用现有的Codable结构体解析这个字符串。
1. 让Codable结构体直接兼容Realm
Realm的Object类支持Codable协议,你可以修改自动生成的结构体(或通过Swagger Codegen的自定义模板),让它们同时继承Object并添加@Persisted属性,保留Codable实现。示例:
import Foundation import RealmSwift class JSONModel: Object, Codable { @Persisted(primaryKey: true) var id: ObjectId @Persisted var alpha: AlphaModel @Persisted var beta: BetaModel @Persisted var gamma: List<GammaModel>? enum CodingKeys: String, CodingKey { case alpha, beta, gamma } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) alpha = try container.decode(AlphaModel.self, forKey: .alpha) beta = try container.decode(BetaModel.self, forKey: .beta) gamma = try container.decodeIfPresent(List<GammaModel>.self, forKey: .gamma) super.init() } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(alpha, forKey: .alpha) try container.encode(beta, forKey: .beta) try container.encode(gamma, forKey: .gamma) } override init() { super.init() } }
Realm的List类型原生支持Codable,可直接对应JSON数组字段。如果用Swagger Codegen,修改模板让生成的结构体自动继承Object并添加@Persisted属性,就能避免手动修改。
2. 用通用转换工具自动映射
编写通用转换函数,利用Swift的Codable能力自动将Codable结构体转为Realm模型,无需手动编写转换逻辑:
// 通用转换函数 func convert<T: Codable, U: Object>(_ codable: T) -> U? { do { let data = try JSONEncoder().encode(codable) let realmObject = try JSONDecoder().decode(U.self, from: data) return realmObject } catch { print("转换失败:\(error)") return nil } } // 使用示例 let jsonData: JSON = ... // API解码得到的结构体 let realmModel = convert(jsonData) as? RealmJSON
只要Realm模型和Codable结构体字段名一致,就能自动完成映射;字段名有差异时,通过CodingKeys调整即可。
3. 你的补充方案:存储原始JSON字符串
这是轻量方案,适合不需要对JSON内部字段做Realm查询的场景:
class RealmJSONContainer: Object { @Persisted(primaryKey: true) var id: ObjectId @Persisted var schemaVersion: String @Persisted var rawJSON: String // 解码回原始Codable结构体 func decodeToJSON() -> JSON? { guard let data = rawJSON.data(using: .utf8) else { return nil } return try? JSONDecoder().decode(JSON.self, from: data) } }
优势是无需维护两套模型,缺点是无法利用Realm的查询能力直接检索JSON内部字段,每次使用都需要解码,性能略低于直接存储Realm模型。
4. 使用Realm DynamicObject动态处理
如果不想定义静态模型,可使用Realm的DynamicObject动态处理JSON数据,无需提前定义模型类:
let realm = try! Realm() try! realm.write { let dynamicObject = DynamicObject() dynamicObject["alpha"] = alphaCodableObject dynamicObject["beta"] = betaCodableObject dynamicObject["gamma"] = gammaCodableArray.map { $0 as Any } realm.add(dynamicObject) }
这种方式失去了类型安全,查询和操作时需手动处理类型,适合快速原型开发或字段不固定的场景。
内容的提问来源于stack exchange,提问作者Joseph

