使用Codable将Realm Document转Swift Object遇编译错误
用Decodable解析Realm聚合查询返回的Document失败问题
问题说明
之前通过document["property"]??.doubleValue手动将Realm的Document转换为Swift嵌套对象,因性能问题改用Decodable优化,但编译报错:
Cannot convert value of type 'Document' (aka 'Dictionary<String, Optional>') to expected argument type 'Data'
代码上下文
尝试的Decodable解析代码
let decoder = JSONDecoder() do { //let resultDocuments = try JSONSerialization.data(withJSONObject: resultDocuments, options: .prettyPrinted) let model = try decoder.decode(Model, from: resultDocuments) //print(model) } catch { print("error: \(error)") }
Document获取方式
通过Realm聚合查询得到Document数组:
// collection was initialised from a Realm database let resultDocuments = try await collection.aggregate(pipeline: somePipeline)
Document类型定义
/// A Dictionary object representing a `BSON` document. public typealias Document = Dictionary<String, AnyBSON?>
返回的Document数据结构示例
[[ "example": Optional(RealmSwift.AnyBSON.double(1096.5171548657413)), "subDocument": Optional(RealmSwift.AnyBSON.array([Optional(RealmSwift.AnyBSON.document([ "startTime": Optional(RealmSwift.AnyBSON.datetime(2023-01-22 07:39:44 +0000)), "someKey": Optional(RealmSwift.AnyBSON.string("2DFB8488-7D7A-48CD-BCCE-652624BBCBF1")), "coordinates": Optional(RealmSwift.AnyBSON.array([Optional(RealmSwift.AnyBSON.double(8.79564924822946)), Optional(RealmSwift.AnyBSON.double(47.498444215458825))])), "subSubDocument": Optional(RealmSwift.AnyBSON.array([Optional(RealmSwift.AnyBSON.document([ "someOtherKey": Optional(RealmSwift.AnyBSON.string("0E425CE8-04C1-458A-87D3-7DE375EDBF50")), "userName": Optional(RealmSwift.AnyBSON.string("ExampleName")), "_id": Optional(RealmSwift.AnyBSON.string("63b3ed0f2e2d45e33bc992d3"))]) )])), "_id": Optional(RealmSwift.AnyBSON.string("1C315817-A9FB-4360-84E9-834083851C33"))])) ])), "_id": Optional(RealmSwift.AnyBSON.string("7a7a3b3b-85ab-435d-b1e9-ef13f17cf3c1")), "someEvenDifferentProperty": Optional(RealmSwift.AnyBSON.string("A7394D67-4EE1-4490-9E36-99DB87FBCDEA")), "example2": Optional(RealmSwift.AnyBSON.bool(false))]]
目标Decodable模型定义
class Model: Identifiable, Equatable, ObservableObject, Decodable { static func == (lhs: Model, rhs: Model) -> Bool { lhs.id == rhs.id } var id: String var subDocument: SubDocument? var example: Double? @Published var example2: Bool = false enum CodingKeys: String, CodingKey { case id = "_id" case subDocument case example case example2 } // this is needed because of @Published I believe required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) subDocument = try container.decode(SubDocument.self, forKey: .subDocument) example = try container.decode(Double.self, forKey: .example) example2 = try container.decode(Bool.self, forKey: .example2) } }
其中SubDocument及嵌套的SubSubDocument均为同结构的Decodable类型。
解决方案
报错核心原因:JSONDecoder仅支持解析Data类型数据,而Document本质是Dictionary<String, AnyBSON?>,无法直接传入。以下是两种可行优化方案:
方案1:使用Realm官方BSONDecoder
Realm提供了专门的BSONDecoder,可直接解码Document类型,无需转换为Data:
import RealmSwift let decoder = BSONDecoder() do { // 注意resultDocuments是数组,需解码为[Model].self let models = try decoder.decode([Model].self, from: resultDocuments) } catch { print("解码失败: \(error)") }
- 确保所有嵌套模型(
SubDocument、SubSubDocument)正确实现Decodable,且CodingKeys与Document中的键完全对应。 - 对于
@Published属性,解码完成后再赋值可避免线程安全问题。
方案2:自定义Document转Model的初始化方法
若无法使用BSONDecoder,可在模型中扩展直接从Document初始化的方法,结合类型校验处理AnyBSON:
extension Model { init(from document: Document) throws { // 处理ID字段 guard let idBSON = document["_id"]?.value, case .string(let id) = idBSON else { throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "ID格式错误")) } self.id = id // 处理嵌套SubDocument if let subDocBSON = document["subDocument"]?.value, case .document(let subDoc) = subDocBSON { self.subDocument = try SubDocument(from: subDoc) } else { self.subDocument = nil } // 处理example字段 if let exampleBSON = document["example"]?.value, case .double(let example) = exampleBSON { self.example = example } else { self.example = nil } // 处理example2字段 if let example2BSON = document["example2"]?.value, case .bool(let example2) = example2BSON { self.example2 = example2 } else { self.example2 = false } } } // 使用方式 let models = try resultDocuments.map { try Model(from: $0) }
- 嵌套模型
SubDocument也需要实现对应的init(from document: Document)方法。 - 这种方式保留了手动解析的灵活性,同时通过批量处理提升性能。
内容的提问来源于stack exchange,提问作者ambassador
相关产品推荐
相关产品推荐

