使用 Mongoose 从 Decimal128 中提取小数 - MongoDB

问题描述 投票:0回答:6

我正在使用 Mongoose 在 Nodejs 中查询 Mongo,并尝试提取存储为 Decimal128 的多个字段的数值。然而,该值奇怪地包含在查询结果中,我不确定如何通过 Mongo 或 Mongoose 提取它:

{data:[
  {
  "date": {
          "$numberDecimal": "1530057600000"
  },
  "open": {
          "$numberDecimal": "86.13"
  },
  "high": {
          "$numberDecimal": "86.63"
  },
  "low": {
          "$numberDecimal": "85.47"
  },
  "close": {
          "$numberDecimal": "85.64"
  },
  "volume": {
          "$numberDecimal": "308508"
  }
},

有没有办法使用 Mongo 或 Mongoose 将上面的 JSON 查询结果转换为下面的结果?

{data:[
 {
  "date": 1530057600000
  "open": 86.13
  "high": 86.63
  "low": 85.47
  "close": 85.64
  "volume": 308508
 },

我尝试按如下方式选择字段,但这不起作用。

    data[i].date.$numberDecimal, 
    data[i].open.$numberDecimal,
    data[i].high.$numberDecimal,
    data[i].low.$numberDecimal, 
    data[i].close.$numberDecimal 

这是我的猫鼬模式:

文件夹 - 模型 - Stock.js

const mongoose = require('mongoose')
mongoose.Promise = global.Promise

const childSchemaData = new mongoose.Schema({
  "_id": false,
  date: {type: mongoose.Types.Decimal128},
  open: {type: mongoose.Types.Decimal128},
  high: {type: mongoose.Types.Decimal128},
  low: {type: mongoose.Types.Decimal128},
  close: {type: mongoose.Types.Decimal128},
  volume: {type: mongoose.Types.Decimal128}
})

const parentSchemaSymbol = new mongoose.Schema({
  "_id": false,
  symbol: {
    type: String,
    trim: true,
    minlength: 2,
    maxlength: 4,
    uppercase: true,
    required: 'Plese enter a valid symbol, min 2 characters and max 4'
  },
  // Array of subdocuments
  data: [childSchemaData],
  slug: String

})

module.exports = mongoose.model('Stock', parentSchemaSymbol)

控制器

const mongoose = require('mongoose')
const parentSchemaSymbol = mongoose.model('Stock')

exports.dbFetch = (req, res) => {
  let curValueDbFetch = req.params.symbol

  const query = { symbol: `${curValueDbFetch}` }
  const projection = { _id: 0, data: 1 }

  parentSchemaSymbol.findOne(query, projection).then(doc => {
    return res.send(doc)
  }).catch(e => {
    console.log(e)
  })
}

我正在将数据发送到前端,这是我在浏览器中收到的:

解决方案

const mongoose = require('mongoose')
const parentSchemaSymbol = mongoose.model('Stock')

exports.dbFetch = (req, res) => {
  let curValueDbFetch = req.params.symbol

  const query = { symbol: `${curValueDbFetch}` }
  const projection = { _id: 0, data: 1 }

  parentSchemaSymbol.findOne(query, projection).sort({ date: -1 }).then(doc => {
    let chartData = doc.data.map(item => {
      return {
        date: parseFloat(item.date), // the date
        open: parseFloat(item.open), // open
        high: parseFloat(item.high), // high
        low: parseFloat(item.low), // low
        close: parseFloat(item.close), // close
        volume: parseFloat(item.volume)// volume
      }
    })
    res.send(chartData)
  })
    .catch(e => {
      console.log(e)
    })
}
javascript node.js mongodb mongoose
6个回答
8
投票

方法一:.

使用 toString()。它将把对象转换为字符串。

find((docs) => {
   let result = docs.map((doc) => {
       if(doc.open){
          doc.open = doc.open.toString();
       }

       if(doc.close){
          doc.close = doc.close.toString();
       }

       return doc;  
   });

    //send modified output
    res.json(result);
})

输出如下:-

/*
[
  {
    "open":  "86.13",
    "close": "85.64"
  },
]
*/

方法2: Mongodb 4.0以上,

db.myCollection.aggregate([
  {$match:{
   //...
   //...
   }},


  { $addFields : {
        open: {"$toString" : "$open"},
        close : {"$toString" : "$close"},
    }},
]);

4
投票

这适用于任何领域!

它也支持子文档和子文档数组

const MySchema = new Schema({/*... schema fields ...*/});


const decimal2JSON = (v, i, prev) => {
  if (v !== null && typeof v === 'object') {
    if (v.constructor.name === 'Decimal128')
      prev[i] = v.toString();
    else
      Object.entries(v).forEach(([key, value]) => decimal2JSON(value, key, prev ? prev[i] : v));
  }
};

MySchema.set('toJSON', {
  transform: (doc, ret) => {
    decimal2JSON(ret);
    return ret;
  }
});

mongoose.model('MyModel', MySchema);

用途:

MyModel.findOne().then(data => console.log(data.toJSON());

0
投票

工作解决方案

const mongoose = require('mongoose')
const parentSchemaSymbol = mongoose.model('Stock')

exports.dbFetch = (req, res) => {
  let curValueDbFetch = req.params.symbol

  const query = { symbol: `${curValueDbFetch}` }
  const projection = { _id: 0, data: 1 }

  parentSchemaSymbol.findOne(query, projection).sort({ date: -1 }).then(doc => {
    let chartData = doc.data.map(item => {
      return {
        date: parseFloat(item.date), // the date
        open: parseFloat(item.open), // open
        high: parseFloat(item.high), // high
        low: parseFloat(item.low), // low
        close: parseFloat(item.close), // close
        volume: parseFloat(item.volume)// volume
      }
    })
    res.send(chartData)
  })
    .catch(e => {
      console.log(e)
    })
}

0
投票

使用 lodash

_.cloneDeepWith()
,这非常简单。迭代每个对象属性并将具有
$numberDecimal
属性的对象转换为字符串。

// first flattenDecimals using mongoose `toJSON()`
var objectStep1= dbResult.toJSON({flattenDecimals: true});
//  use lodash _.cloneDeepWith() to iterate over every object property
var returnThisObject = _.cloneDeepWith(objectStep1, propVal =>{
    if (_.has(propVal, '$numberDecimal')) return propVal.$numberDecimal;
});

或者,您可以在没有

toJSON()
的情况下按照下面的方式执行此操作,但我认为效率会较低,因为 mongoose 结果具有如此多的属性,这些属性不属于结果的一部分。然后您还需要检查未定义的属性。

var returnThisObject = _.cloneDeepWith(dbResult, propVal =>{
    if (!propVal) return propVal; // check for undefined 
    if ('Decimal128' == propVal ._bsontype) return propVal.toString();
});

0
投票

在我的例子中,即使数据库文档有键,值也是未定义的,因为在模式中我忘记添加该字段,但我试图读取该值。


0
投票

使用 JS(使用 TypeScript)的另一种选择

function mutateDocumentWithNumberDecimals<T extends { $numberDecimal?: string } | Record<string, any>>(
  obj: T
): any {
  if (!obj || typeof obj !== "object") return obj;
  if (Array.isArray(obj)) {
    return obj.map(mutateDocumentWithNumberDecimals);
  }
  if (Object.hasOwn(obj, "$numberDecimal")) {
    // check if possible to keep precision when converting to a number, if so, return a number
    const numberDecimalValue = obj["$numberDecimal"];
    if (typeof numberDecimalValue === "string") {
      const numberValue = Number(numberDecimalValue);
      if (numberValue.toString() === numberDecimalValue) {
        return numberValue;
      }
    }
    return numberDecimalValue;
  } else {
    for (const key in obj) {
      obj[key] = mutateDocumentWithNumberDecimals(obj[key]);
    }
  }
  return obj;
}

// Usage:
const mutated = mutateDocumentWithNumberDecimals(obj);
© www.soinside.com 2019 - 2024. All rights reserved.