0

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 2

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);
})
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the answer, but db.version() returns 3.6.21 :/
@Eria updated the answer to include the older way of updating documents in MongoDB. Please have a try and see if it works for you.
I can't use this as-is in Java. The Javascript part does not compile. I will try to find a way to adapt it.
@Eria you dont need to use this in java. You just need to open a mongo shell and execute the script. Check out Mongo shell. It should come natively with your MongoDB.
Ultimately, I have to put this in a mongock change log, integrated in our app source code. So, yes, I need to translate this in Java ^^'
|
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.