Search code examples
mongodbobjectidsubdocument

Sails and subdocument ObjectId()


I have an existing MongoDB collection which is basically looks something like:

users: [
{
    "_id": ObjectId("56a6f714a2c56f1c3b0f17f1"),
    "name": "Daniel",
    "phones" : [
        {
            "_id": ObjectId(""56a78dd1879c40ea63822141"),
            "areacode": 333,
            "number": 111111111
        },
        {
            "_id": ObjectId(""56a78dd1879c40ea63822141"),
            "areacode": 111,
            "number": 99999999
        }

    ]

},
{
    "_id": ObjectId("56a6f714a2c56f1c3b0f17f1"),
    "name": "John",
    "phones" : [
        {
            "_id": ObjectId(""56a78dd1879c40ea63822141"),
            "areacode": 333,
            "number": 111111111
        },
        {
            "_id": ObjectId(""56a78dd1879c40ea63822141"),
            "areacode": 111,
            "number": 99999999
        }
    ]

}
]

As you can see, I'm using Object ID in the subdocuments to relate this data with a external collection where we store additional info on the numbers. All these ID's were automatically generated by Mongoose in other app which does that automatically.

Now, in Waterline, there is no schema support for subdocuments, and therefore when doing a find() on the collection, the subdocuments ObjectId will be returned as JSON, instead of the ID string.

This result in something like

results: [
{
    "id": "56a6f714a2c56f1c3b0f17f1",
    "name": "Daniel",
    "phones" : [
        {
            "_id": {
                "_bsontype": "ObjectID",
                "id": "V§zÐ\u0019}dÒÏ_"
             }
            "areacode": 333,
            "number": 111111111
        },
        {
            "_id": {
                "_bsontype": "ObjectID",
                "id": "V§zÐ\u0019}dÒÏ_"
             }
            "areacode": 111,
            "number": 99999999
        }

    ]

}
]

Mongoose handles this gracefully, and you always can have those id's available for performing related queries on the client side, but with Waterline, and no nested schemas, this seems to be another dead end.

Is there any way to solve this without looping over the entire collection before returning it, having to migrate databases, change documents or having to normalize the hell out of the database? This data is accessed by several apps and needs to remain as it is.


Solution

  • There is probably a better way to do this, but in the end I decided to iterate recursively on the returned JSON, replacing the ID's with a proper ObjectID, thanks to the bson-objectid library.

    What I basically did was calling this method on the toJSON function at the model level:

    // Recursively iterate over a JSON object
    function replaceBSONIDs(object){
    
       var ObjectID = require("bson-objectid");
    
      for(var x in object){
    
        if(typeof object[x] == 'object') {
          replaceBSONIDs(object[x]);
        } else {
    
            // Perform the actual replace of the _id with an object ID
            if('_id' in object) {
                object.id = ObjectID(object._id.id);
                delete object._id           
        }
      }
    
    }
    

    The issue sounds like a waterline bug, so opening an issue. Hope this helps someone else in the meantime.