Skip one nested level of subdocument in Mongoose

409 views Asked by At

I have a parent schema with a subdocument. The subdocument has a property with an array of embedded objects:

Child schema

var snippetSchema = new Schema({
    snippet: [
        {
            language: String,
            text: String,
            _id: false
        }
    ]
});

Parent schema

var itemSchema = new Schema({
    lsin: Number,
    identifier: {
        isbn13: Number,
    },
    title: snippetSchema,
});

Which upon Item.find returns an object like so:

[
    {
        _id: (...),
        lsin: 676765,
        identifier: {
            isbn13: 8797734598763
        },
        title: {
            _id: (...),
            snippet: [
                {
                    language: 'se',
                    text: 'Pippi Långstrump'
                }
            ]
        }
    }
]

I would like to skip one nested level of the subdocument when the object is returned to the client:

[
    {
        _id: (...),
        lsin: 676765,
        identifier: {
            isbn13: 8797734598763
        },
        title: {
            language: 'se',
            text: 'Pippi Långstrump'
        }
    }
]

So far I have tried:

#1 using a getter

function getter() {
    return this.title.snippet[0];
}

var itemSchema = new Schema({
    ...
    title: { type: snippetSchema, get: getter } 
});

But it creates an infinite loop resulting in RangeError: Maximum call stack size exceeded.

#2 using a virtual attribute

var itemSchema = new Schema({
    ..., {
    toObject: {
        virtuals: true
    }
});

itemSchema
    .virtual('title2')
    .get(function () {
        return this.title.snippet[0];
});

Which will generate the desired nested level but under a new attribute, which is not acceptable. To my knowledge there is no way of overriding an attribute with an virtual attribute.

The question is: is there any other way to go about in getting the desired output? There will be several references to the snippetSchema across the application and a DRY method is preferred.

I am new to MongoDB and Mongoose.

1

There are 1 answers

2
Kevin Smith On BEST ANSWER

You'll need to use the $project within an mongodb aggregation pipeline.

Within my database I have the following:

> db.items.find().pretty()
{
        "_id" : 123,
        "lsin" : 676765,
        "identifier" : {
                "isbn13" : 8797734598763
        },
        "title" : {
                "_id" : 456,
                "snippet" : [
                        {
                                "language" : "se",
                                "text" : "Pippi Långstrump"
                        }
                ]
        }
}

Then we just need to create a simple aggregation query:

db.items.aggregate([
    {$project: { lsin: 1, identifier: 1, title: { $arrayElemAt: [ '$title.snippet', 0 ] }}}
])

This just uses a $project (https://docs.mongodb.com/v3.2/reference/operator/aggregation/project/) and a $arrayElemAt (https://docs.mongodb.com/v3.2/reference/operator/aggregation/arrayElemAt/) to project the first item out of the array. If we execute that we will get the following:

{
        "_id" : 123,
        "lsin" : 676765,
        "identifier" : {
                "isbn13" : 8797734598763
        },
        "title" : {
                "language" : "se",
                "text" : "Pippi Långstrump"
        }
}