0

I had performance issues due to converting a Document to a Swift Object doing something like document["property"]??.doubleValue for each property and property of embedded Documents. So I now figured out the best way to do this would probably be the Decodable. So I tried:

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)")
        }

Which fails to compile due to Cannot convert value of type 'Document' (aka 'Dictionary<String, Optional>') to expected argument type 'Data'


More context

From an aggregate call I receive a Document from a

// collection was initialised from a Realm database
let resultDocuments = try await collection.aggregate(pipeline: somePipeline)

where the result is of type Document:

/// A Dictionary object representing a `BSON` document.
public typealias Document = Dictionary<String, AnyBSON?>

The result looks like this:

[[
"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))]]

This is the Swift Decodable object I want to convert the result to:

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)
    }

Then there is a subdocument that looks very similar and contains another SubSubDocument, which only contains some Strings.

P.S. This question was asked after finding out the best way to achieve this would probably be to use a Codable. See this question for reference.

D. Kee
  • 169
  • 14
  • Optional values in a Swift dictionary make no sense because `nil` means *no key*. And `Codable` does not support `Any` at all. **All** values in a dictionary **must** conform to `Codable`, period. – vadian Jan 22 '23 at 12:07
  • @vadian I assume you were referring to `document["property"]??.doubleValue`? I added the raw data of the response to illustrate why exactly this way of unwrapping was necessary.. If not, could you elaborate your point? – D. Kee Jan 22 '23 at 12:28
  • I do not quite agree with the statement *"Optional values in a Swift dictionary make no sense"* if you are referring to `Dictionary`. This is simply how Realm has implemented the `Document`. It's not my choice, it's what is used by this SDK... It is also not the key that is optional, but the value of type AnyBSON – D. Kee Jan 22 '23 at 12:30
  • I don’t know Realm but it seems that it fights the Swift language semantics. If you assign `nil` to a key In a **Swift** dictionary the key will be removed. – vadian Jan 22 '23 at 15:07
  • I am mostly concerned about "performance issues due to converting a Document to a Swift Object". As mentioned in my [previous answer](https://stackoverflow.com/questions/75175460/performant-conversion-to-swift-object-from-mongodb-graphql-result/75194196?noredirect=1#comment132699431_75194196) that code 'converted' (instantiated) 10,000 documents in about second - and that really has very little to do with Realm. Codable is powerful and adds convenience and tighter code but I am not sure there are any crazy performance benefits. – Jay Jan 22 '23 at 15:35
  • @vadian For MongoDB Atlas (and Realm) nil values are represented by null (NSNull in swift) for optionals in the back-end database. e.g. for AnyBSON, null is a valid value. When evaluated, it can return an nil, so it has to be double unwrapped e.g. `let x = document?["optionalValue"]??.stringValue` to get either the value or an actual nil. – Jay Jan 22 '23 at 16:04

0 Answers0