You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 01:05:18