1

I have a hierarchy of codable Swift structs that I need to store in MongoDB in the cloud (Atlas). I do not need to persist these structs locally, and this app won't need to access this data once it's stored. (There are other apps accessing it, but that's out of the scope of this question.)

MongoDB used to provide a solution called Stitch that did allow me to do just that: my iOS app had a codable Swift struct, the iOS app connected to a Stitch app on MongoDB's Atlas cloud, and I was able to insert the struct into a collection in the DB.

With Stitch, I was able to just do it like this:

let itemsCollection = mongoServiceClient.db(“myDatabase”).collection(“myCollection”,
    withCollectionType: MyStruct.self)
itemsCollection.insertOne(myStruct) 

Now Stitch is apparently deprecated and replaced by Realm, a formerly-third-party local persistency layer that MongoDB acquired and integrated with their Atlas cloud.

Realm isn't backwards-compatible with Stitch, and it no longer seems to be able to map Swift structs to a MongoDB backend: it can now sync Realm objects, i.e. ObjC-bridged subclasses of an abstract base class that Realm defines. Converting dozens of structs that my app uses in multiple places into such classes would be way too involved, especially since I would only do that for the sole purpose of uploading them to a backend as I do not need any of the (no doubt excellent) core functionalities of Realm.

(By the way, I find the migration from structs to objects, especially with an Objective-C foundation, quite baffling, as it goes very much against the flow: it's quite clear that Swift and value types are the future for Apple's platforms...)

My question: is there a way I may have missed to just connect to a Realm app on Atlas, and insert documents defined as Swift structs into a collection in an Atlas DB?

Or is there a reasonably-easy way to convert JSON into a Realm object? From what I read, I'd still need to define a schema for the object, and since my source struct contains several embedded structs, it would require creating Object subclasses for each, which is something I'd really need to avoid. Basically, I designed this functionality of my app around MongoDB and Stitch, and I'm not ready to suddenly accommodate the limitations of Realm for no added value.

Finally, failing all that, is there a way to encode my structs into a format that some official MongoDB API (e.g. Realm) can use for inserting?

I can already encode my struct into JSON, but that doesn't seem to be enough. I have seen the doc about MongoDB Realm Remote Access, but it doesn't say anything about my use case beyond connecting to a DB.

Error messages I get suggest that I would need to create a Document (aka Dictionary<String, Optional<AnyBSON>>), where AnyBSON is an enum that defines the type of the BSON value. I haven't found any documentation about converting anything into that format: is there an API for that, or do I need to break down my struct or JSON into a hierarchy of AnyBSON values?

pua666
  • 326
  • 1
  • 7
  • 1
    This question is pretty hard to follow. Are you asking about MongoDB, or about Realm? What's Atlas? Part of it also reads like "I want to use Realm but I don't want to use Realm because I don't like its API", which is ... confusing, I'm not really sure what you're looking for – Alexander Jun 03 '21 at 23:29
  • Stitch? Atlas? What are they, and what do they have to do with developing what in Swift? – El Tomato Jun 03 '21 at 23:32
  • @Alexander the question is for people who know MongoDB and Realm, obviously. And I don't see how my question is confusing if you read it carefully: it's the last paragraph. – pua666 Jun 04 '21 at 09:30
  • I think that might limit the number of readers/answerers you get, because someone who only knows MongoDB or only knows Realm won't be able to help because they don't know the other. I think you would have better success if you phrase your question in a more detailed fashion that reduces the background knowledge potential answerers would need to know. – Alexander Jun 04 '21 at 13:47
  • I appreciate that, but this is a very specific question about the Swift SDK for Realm. I understand that there aren’t too many users of the combination, but this is it. – pua666 Jun 04 '21 at 17:13
  • MongoDB Realm is a different platform than Stitch and Atlas and has it's own language constructs. While you may seem some similarities, it's Realm, not Stitch. So, for example, if you want to know what a [Collection](https://docs.mongodb.com/realm/sdk/ios/data-types/collections/) is, it's defined in the SDK guide (just like it would be in any different programming language. Familiarizing yourself with it using the getting started guide is really the best first step to learning a new language. To answer your question... – Jay Jun 04 '21 at 18:11
  • ... You can interact with Realms back end database (called Atlas) in two ways. 1) Use Realm as is - create powerful, flexible and scalable Realm objects and let the SDK synchronize those objects for you. 2) You can read/write directly to Atlas using [MongoDB Realm Remote Access](https://docs.mongodb.com/realm/sdk/ios/examples/mongodb-remote-access/) which essentially bypasses a lot of local interaction. It's quite clunky (IMO) and really doesn't leverage the power and simplicity of Realm. However, that's probably what you want. – Jay Jun 04 '21 at 18:15
  • Thanks, @Jay. To clarify: I've built exactly what I needed, with Stitch: mapping Swift structs to a MongoDB instance in Atlas (where the Stitch layer was almost invisible). Now that Stitch is deprecated, I need to move it all to Realm, which is primarily a local persistency model based on Objective-C whose features I don't need, and migrating my hierarchical structs to Realm "objects" would mean recursively reimplementing each as ObjC-bridged classes. – pua666 Jun 04 '21 at 22:05
  • I am familiar with Realm but it's not clear what you are asking. If you don't need or want to use Realm Objects then use the process I outlined in 2) above where you don't need any Realm objects - you can continue to use your existing structs. The only coding you'll need is to map the downloaded documents from Atlas to your structs. Again, this is covered in the documentation linked above. Maybe you can further clarify (in the question) what you're after as it seems like that's a good fit for your use case. – Jay Jun 05 '21 at 15:31
  • @Jay I wonder if you've seen the edits to my question! I'm not downloading anything, only inserting, which was a two-step procedure in Stitch (map struct to collection; insert struct to collection – one method call each), and seems to be way more involved in Realm. As for inserting (option 2), I need to map my struct to `Document`, which is a Dictionary with a string and an optional `AnyBSON` enum, and I haven't found any docs about how to create such a thing. I can flatten my struct into JSON, but the API doesn't seem to handle it. Thanks again for your help so far! Appreciate it. – pua666 Jun 05 '21 at 21:04
  • Are you saying that your app is web-based and no data moves from the server to your device (i.e. downloading)? Either way, inserting using Remote Access is exactly as you describe. For example suppose I have Task document and want to insert it into a collection. Here's the line of code that does that: `collection.insertOne(myTaskDoc, { result in })`. `Document` is in the Reference guide but the format is `let taskName = AnyBSON(stringLiteral: "Inserted Task")` and then `let taskDict = ("name", taskName )` followed by `let myTaskDoc = Document(dictionaryLiteral: taskDict)`. – Jay Jun 06 '21 at 14:55
  • @Jay it's a Swift iPhone app with Swift structs that I want to insert into MongoDB Atlas. The same app won't read back the data; some other apps will. I really don't understand your example: my struct is a Swift struct with several other embedded Swift structs. I just want to put it into MongoDB, and I have no idea why I need a name, what any of these string literals are, or, once again, how to convert a Swift struct into a format that I can insert… With Stitch, I could insert it directly. I also have a JSON of my struct, I wish I could just insert it with no further hassle. – pua666 Jun 06 '21 at 18:07
  • @Jay can you please read my post after its edits? I think it should be pretty clear now. Unfortunately, I don't see any reference (not even in your comments) to how I can (a) convert a struct into something Realm could insert, or (b) insert a JSON… Maybe I should not be using the Realm API but the Mongo Swift driver? – pua666 Jun 06 '21 at 18:13

1 Answers1

2

I'll preface this answer by stating it's not a specific answer but more guidance on how to accomplish what's asked in the question.

First, let me re-state the question

What is the process to insert data into MongoDB Realm without storing it locally or using Realm objects; use Classes, Structs (or something else) instead.

To illustrate, we have some tasks stored in MongoDB Realm (Atlas) with nothing stored locally. Here I will create a Document that contains task information and insert it.

We first need to define where the data will be stored and what collection to store it in:

let app = Your App
let client = app.currentUser!.mongoClient("mongodb-atlas")
let database = client.database(named: "task-database")
let collection = database.collection(withName: "TaskClass")

then we'll create some BSON's to store the textual data in

let _id = AnyBSON(ObjectId.generate())
let taskName = AnyBSON(stringLiteral: "Inserted Task")
let status = AnyBSON(stringLiteral: "Open")
let partition = AnyBSON(stringLiteral: Constants.REALM_PARTITION_VALUE)

then, for each BSON, give each a key: value pair (Dictionary)

let idDict = ("_id", _id)
let taskDict = ("name", taskName )
let statusDict = ("status", status)
let partitionDict = ("_partitionKey", partition)'

finally we'll create and write a Document to MongoDB Realm

let myTaskDoc = Document(dictionaryLiteral: idDict, taskDict, statusDict, partitionDict)

collection.insertOne(myTaskDoc, { result in
    print(result)
})

The above code answers the question

Can the MongoDB Realm Swift API still just insert a struct into an Atlas collection, like in Stitch?

Yes! In fact I did it without using a Struct at all. Keeping in mind the above code is really for simplicity - obviously leveraging Structs or Classes will provide a lot more flexibility and encapsulation of your data. This would be accomplished by crafting or updating an existing class or struct so when data is to be written utilize a function to set up the properties in AnyBSON format and return a Document.

class TaskClass {
   var name = ""

   func toBson() -> Document {
       let taskName = AnyBSON(stringLiteral: self.name)
       let taskDict = ("name", taskName )
       let myTaskDoc = Document(dictionaryLiteral: taskDict) //abbreviated
       return myTaskDoc
}

then to use

let doc = myTask.toBson()
collection.insertOne(doc, { result in
   print(result)
})

This could easily be expanded on using Codable and JSONEncoder() to directly store JSON data in your structs, and then send BSON data to the server

Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thanks; this is interesting, and I may use it in some circumstances. However, my issue is with having an existing struct with embedded sub-structs, which I can encore into JSON, and which I was very easily able to insert into MongoDB via Stitch. What you describe is useful as now I know how I can construct BSON data for Realm; however, this still involves manually reconstructing a complex struct. I will probably end up (1) using Stitch while it’s compatible with Atlas, (2) finding a JSON-to-BSON conversion mechanism, or (3) try to integrate old Stitch functionality back into the Realm API. – pua666 Jun 07 '21 at 22:04
  • Actually, I'm thinking about a solution involving [result builders](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md) to construct a BSON object based on your answer. (I just tried to use `SwiftBSON` (even though it's not officially supported on iOS), but its `Document` type is not the same as the identically-named type used by Realm, and there doesn't seem to be an easy way to convert between them… so using that would mean bypassing Realm completely.) – pua666 Jun 08 '21 at 10:26