The JOKER
The JOKER

Reputation: 473

mongoDB returning with $numberDecimal in the response of a query

Below is my mongoose schema

const mongoose = require('mongoose');

const AccountingCostsSchema = mongoose.Schema(
    {
        // other properties goes here
        accountCosts: {
            type: mongoose.Decimal128,
            set: (v) =>
            mongoose.Types.Decimal128.fromString(parseFloat(v).toFixed(4)),
            required: true
        }
    },
    {
        collection: 'accountingCosts'
    }
);

export = mongoose.model('AccountingCosts', AccountingCostsSchema);

Data in MongoDB

accountingCosts collection

Data in Text mode view

{
    "_id" : ObjectId("4e1eb38c2bdea2fb81f5e771"),
    "accountId" : ObjectId("4e8c1180d85de1704ce12110"),
    "accountCosts" : NumberDecimal("200.00"),
}

Data in Text mode view

Data in Text mode view

My query

db.getCollection('accountingCosts').find({'accountId': '4e8c1180d85de1704ce12110'})

Result from query

"accountCosts": {
      "$numberDecimal": "123.00"
}

I tried writing a getter function on schema like i have a setter function. But it is not working

get: function(value) {
      return value.toString();
}

My expected output is just a plain property with name and value like below

"accountCosts": "123.00"

Upvotes: 8

Views: 6127

Answers (1)

MrHim
MrHim

Reputation: 421

I am sure you found a solution, but i will also write it here so who will land here would find a solution. Your idea to using a getter was right, but maybe you forgot to enable it in the schema, so let' s see how using your code.

You have this schema:

var AccountingCostsSchema = new Schema({
    accountId: String,
    accountCosts: {
        type: Schema.Types.Decimal128,
        default: 0
    }
});

So when you retrieve it you will get:

{
    "_id" : "12345",
    "accountId" : "123456",
    "accountCosts" : {
        "$numberDecimal": "123.00"
    }
}

Taking in example that you would get accountCosts as number, so something like that:

{
    "_id" : "12345",
    "accountId" : "123456",
    "accountCosts" : 123
}

We would need a getter as you said, so we will need to add a function of this getter in our model file, let' s say after your schema. This could be your getter function:

function getCosts(value) {
    if (typeof value !== 'undefined') {
       return parseFloat(value.toString());
    }
    return value;
};

Now, let' s declare this getter in our schema that will become:

var AccountingCostsSchema = new Schema({
    accountId: String,
    accountCosts: {
        type: Schema.Types.Decimal128,
        default: 0,
        get: getCosts
    }
});

Now mongoose will uderstand how you want that value is returned, but we have to specify that we want it uses the getter. So we have to enable it when we want to retieve the value in json format. We can simply add it as this:

var AccountingCostsSchema = new Schema({
    accountId: String,
    accountCosts: {
        type: Schema.Types.Decimal128,
        default: 0,
        get: getCosts
    }
}, {toJSON: {getters: true}});

And so, when you will retrieve it, you will get this:

{
    "_id" : "12345",
    "accountId" : "123456",
    "accountCosts" : 123,
    "id" : "12345"
}

But, whoa (this is not my batman glass!) what is that "id" : "12345"? According to Mongoose documents:

Mongoose assigns each of your schemas an id virtual getter by default which returns the document's _id field cast to a string, or in the case of ObjectIds, its hexString. If you don't want an id getter added to your schema, you may disable it by passing this option at schema construction time.

How we can avoid it? We can just add id: false in our schema that now will be:

var AccountingCostsSchema = new Schema({
    accountId: String,
    accountCosts: {
        type: Schema.Types.Decimal128,
        default: 0,
        get: getCosts
    },
    id: false
}, {toJSON: {getters: true}});

And now you will not see it anymore.

Just a little last tip (no, i have not finished): What happens if you have an array of objects in your schema like this?

var AccountingCostsSchema = new Schema({
    accountId: String,
    usersAndCosts: [{
        username: String,
        accountCosts: {
            type: Schema.Types.Decimal128,
            default: 0,
            get: getCosts
        }
    ],
    id: false
}, {toJSON: {getters: true}});

Bad news now: you can not keep on using this schema. Good news now: you can fix it easly. You need to create a new schema for usersAndCosts and then reference it in the parent schema. Let' s see it:

var usersAndCostsSchema = new Schema({
    username: String,
    accountCosts: {
        type: Schema.Types.Decimal128,
        default: 0,
        get: getCosts
    },
    id: false
}, {toJSON: {getters: true}});

var AccountingCostsSchema = new Schema({
    accountId: String,
    usersAndCosts: [usersAndCostsSchema],
    id: false
}, {toJSON: {getters: true}});

The result will be the same, your accountCosts will be shown as number, no double ids. Remember that of course this is to enable getters for toJSON but you could need to enable them for toObject too, as you could need to do the same for setters. But we here talked about getters for JSON.

Upvotes: 17

Related Questions