我正在使用 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)
})
}
方法一:.
使用 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"},
}},
]);
这适用于任何领域!
它也支持子文档和子文档数组
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());
工作解决方案
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)
})
}
使用 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();
});
在我的例子中,即使数据库文档有键,值也是未定义的,因为在模式中我忘记添加该字段,但我试图读取该值。
使用 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);