skip to Main Content

I have an existing collection, containing several documents.

[{
    "_id": "...1",
    "prop1": "...",
    "prop2": "...",
    "someArray": [
        {
            "value": "sub element 1.1"
        },
        {
            "value": "sub element 1.2"
        },
        {
            "value": "sub element 1.3"
        }
    ]
}, {
    "_id": "...2",
    "prop1": "...",
    "prop2": "...",
    "someArray": [
        {
            "value": "sub element 2.1"
        },
        {
            "value": "sub element 2.2"
        }
    ]
}, // many others here...
]

For each root document, I would like to add an _id property of type ObjectId on each sub-element of someArray. So, after I run my command, the content of the collection is the following:

[{
    "_id": "...1",
    "prop1": "...",
    "prop2": "...",
    "someArray": [
        {
            "_id": ObjectId("..."),
            "value": "sub element 1.1"
        },
        {
            "_id": ObjectId("..."),
            "value": "sub element 1.2"
        },
        {
            "_id": ObjectId("..."),
            "value": "sub element 1.3"
        }
    ]
}, {
    "_id": "...2",
    "prop1": "...",
    "prop2": "...",
    "someArray": [
        {
            "_id": ObjectId("..."),
            "value": "sub element 2.1"
        },
        {
            "_id": ObjectId("..."),
            "value": "sub element 2.2"
        }
    ]
}, // ...
]

Each ObjectId being, of course, unique.

The closer I got was with this:

db.getCollection('myCollection').updateMany({}, { "$set" : { "someArray.$[]._id" : ObjectId() } });

But every sub-element of the entire collection ends up with the same ObjectId value…

Ideally, I need to get this working using Java driver for MongoDB. The closest version I got is this (which presents the exact same problem: all the ObjectId created have the same value).

database
    .getCollection("myCollection")
    .updateMany(
        Filters.ne("someArray", Collections.emptyList()), // do not update empty arrays
        new Document("$set", new Document("someArray.$[el]._id", "ObjectId()")), // set the new ObjectId...
        new UpdateOptions().arrayFilters(
            Arrays.asList(Filters.exists("el._id", false)) // ... only when the _id property doesn't already exist
        )
    );

2

Answers


  1. Chosen as BEST ANSWER

    Here is what I managed to write in the end:

    MongoCollection<Document> collection = database.getCollection("myCollection");
    collection
        .find(Filters.ne("someArray", Collections.emptyList()), MyItem.class)
        .forEach(item -> {
            item.getSomeArray().forEach(element -> {
                if( element.getId() == null ){
                    collection.updateOne(
                        Filters.and(
                            Filters.eq("_id", item.getId()),
                            Filters.eq("someArray.value", element.getValue())
                        ),
                        Updates.set("someArray.$._id", new ObjectId())
                    );
                }
            });
        });
    

    The value property of sub-elements had to be unique (and luckily it was). And I had to perform separate updateOne operations in order to obtain a different ObjectId for each element.


  2. With MongoDB v4.4+, you can use $function to use javascript to assign the _id in the array.

    db.collection.aggregate([
      {
        "$addFields": {
          "someArray": {
            $function: {
              body: function(arr) {
                      return arr.map(function(elem) {
                               elem['_id'] = new ObjectId(); 
                               return elem;
                             })
                    },
              args: [
                "$someArray"
              ],
              lang: "js"
            }
          }
        }
      }
    ])
    

    Here is the Mongo playground for your reference. (It’s slightly different from the code above as playground requires the js code to be in double quote)


    For older version of MongoDB, you will need to use javascript to loop the documents and update them one by one.

    db.getCollection("...").find({}).forEach(function(doc) {
      doc.someArray = doc.someArray.map(function(elem) {
        elem['_id'] = new ObjectId(); 
        return elem;
      })
      db.getCollection("...").save(doc);
    })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search