Adding into embedded document

I’m trying to add images along with tags to my collection. The schema is like this:

user = {
                    '_id': email,  
                    'username': username,
                    'password': hashed,
                    'images': {
                            'tags': []
                    }
            }

When trying to add a photo to an already existing user I did this:

image = {
         '_id': _id,
         'tags': tags
         }
mongo.db.users.update_one(
         filter = { '_id': session['email']},
         update = { '$addToSet': {'images': image}}
)

But I got an error: “Cannot apply $addToSet to non-array field. Field named ‘images’ has non-array type object”. Can someone help?

Hello, @cosmina! Welcome to the community! :wave:

You get this error, because you’re trying to use object as an array.
To insert your new document into ‘images’ field, you must convert it to an array first.

To illustrate it, assume, you have the following dataset:

var img1 = {
  _id: 1,
  url: 'http://example.com/pics/1',
  tags: ['nature', 'africa'],
};

var img2 = {
  _id: 2,
  url: 'http://example.com/pics/2',
  tags: ['animals', 'dogs'],
};


db.test1.insertOne({
  _id: 'A',
  images: img1,
});

You can do the type-conversion for ‘images’ prop for each document separately, at the same time you insert a new image to it:

db.test1.update(
  // query the document(s), you need to update
  {
    _id: 'A'
  },
  [
    {
      $addFields: {
        images: {
          $cond: {
            if: {
              // if field is array
              $eq: [
                { $type: '$images' },
                'array'
              ]
            },
            then: {
              // then add new image to it
              $setUnion: [
                '$images',
                [img2]
              ]
            },
            else: {
              // else convert it to array
              // and add image to it
              $setUnion: [
                ['$images'],
                [img2]
              ]
            }
          }
        },
      }
    },
  ]
);

But IMHO, it is better to migrate schema for all the documents in your collection with this operation:

db.test1.updateMany(
  // leave empty object here,
  // so every document will be patched
  {},
  [
    {
      $addFields: {
        images: {
          $cond: {
            if: {
              // if field is array
              $eq: [
                { $type: '$images' },
                'array'
              ]
            },
            then: '$images', // then do not touch it
            else: ['$images'], // else convert it to an array
          }
        }
      }
    }
  ]
);

After this migration, the insertion into ‘images’ array will be as easy as:

db.test1.updateOne(
  { _id: 'A' },
  {
    $addToSet: {
      images: img2,
    }
  }
);

Both operations use updates with aggregation pipeline.

1 Like

Thank you so much for the detailed response! :slight_smile:

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.