I am using Redis in my backend to scale subscriptions. I am using this library to implement redis on top of my javascript code. And using mongoose for the models.
During a redis publish, I have to stringify the objects that I get from mongoose. I parse them on the subscribing end and it all works well until there's a nested object in the object that needs to be stringify-ed.
So if my object is this:
{ subtitle: '',
description: '',
checklists:
[ { _id: 5cee450c0fa29d0b54275da0, items: [] },
{ _id: 5cee455c0c31785b0875e09d, items: [] },
{ _id: 5cee47dc6d32e72c6411ce2d, items: [] } ],
attachments: [],
labels: [],
_id: 5ced1af26547233798f943f6,
title: 'asfasf',
box: 5cece1c3e6c3c13ff098658d,
workflow: 5cece1cbe6c3c13ff0986591,
}
I receive:
{ cardUpdated:
{
subtitle: '',
description: '',
checklists: [ [Object], [Object], [Object] ],
attachments: [],
labels: [],
_id: '5ced1af26547233798f943f6',
title: 'asfasf',
box: '5cece1c3e6c3c13ff098658d',
workflow: '5cece1cbe6c3c13ff0986591',
}
}
When I publish I use the following line:
pub.publish(types.CARD_UPDATED,
JSON.stringify(
{ cardUpdated: await getUpdatedCardStats(checklist.card) },
));
Note: I know that I am wrapping the argument for stringify in {} and without it the nested objects would not be ignored, but I need to do that because I need the key property name on the subscription end i.e. I am using this publish command with different key names at several places.
Is there a way to about this to get the nested objects stringify-ed?
EDIT: Turns out, I was getting the proper full object as a string on the subscribing end of Redis, but it was actually JSON.parse() that was the culprit. After parsing, it completely ignores the nested objects. Is there anyway to avoid this?
Try:
const value = JSON.stringify({
cardUpdated: await getUpdatedCardStats(checklist.card)
});
pub.publish(types.CARD_UPDATED, value);
This is not a valid JS object:
{ _id: 5cee450c0fa29d0b54275da0, items: [] }
I think it's the output of .toString() of an object of type {_id: ObjectId, items: any[], with ObjectId defined here. In any case, the JSONification of this object is not trivial and that is why JSON.stringify simply outputs [Object].
To bypass this limitation, you might implement a custom function to transform your object into one that can be trivially JSONified, possibly with the help of ObjectId.toString().
Related
looking for some help here.
I have a basic postgres select statement that simply returns a JSONb column.
******* Edit to add SQL query ************
anpool.query(`SELECT data as test
FROM appanalytics
WHERE to_date(data->>'dateTime', 'MM/DD/YYYY')
BETWEEN $3
AND $4
AND cid=$1 AND aid=$2`,[req.session.cID,req.session.AccountID,endDate,startDate]).then(analytics =>{
//This returns an array of objects, each object in the array
// is wrapped in the column name as shown above
console.log(analytics)
});
Now this works fine however each object in the array is wrapped in the table name
[
{
test: {
ip: '10.10.10.10',
atID: 0,
uuID: '084B98DE-6D89-490E-8238-E25DDC8E5C04',
itemID: '0',
timeEnd: '2021-07-28T13:40:16.788Z',
actionID: 'speakerMainDetail',
dateTime: '07/28/2021',
location: [Object],
timeStart: '2021-07-27T12:54:21.284Z',
deviceType: 'web'
}
}
]
So, in this response you can see, the object is nested in 'test' I need it to be the top level, so each response object is not nested in test, any ideas on how that might be possible? Using nodeJS and pg npm.
Thanks
You get back an array of rows, each an object with the columns you were selecting. There's not really anything you can or should do against that - notice you cannot use ip, atID etc as column names since (presumably) these are dynamic. You can do
anpool.query(
`SELECT data as test
FROM appanalytics
WHERE to_date(data->>'dateTime', 'MM/DD/YYYY')
BETWEEN $3
AND $4
AND cid=$1 AND aid=$2`,
[req.session.cID,req.session.AccountID,endDate,startDate]
).then(result => {
const analytics = result.rows.map(row => row.test);
console.log(analytics);
});
You can extract it like this:
const res = [
{
test: {
ip: '10.10.10.10',
atID: 0,
uuID: '084B98DE-6D89-490E-8238-E25DDC8E5C04',
itemID: '0',
timeEnd: '2021-07-28T13:40:16.788Z',
actionID: 'speakerMainDetail',
dateTime: '07/28/2021',
location: [Object],
timeStart: '2021-07-27T12:54:21.284Z',
deviceType: 'web'
}
}
];
const { test } = res[0];
console.log(test);
I have a structure like this:
{
...
_id: <projectId>
en-GB: [{
_id: <entryId>,
key: 'some key',
value: 'some value',
}]
}
And I've tried updating it with Mongoose (and raw mongodb too) like this:
const result = await Project
.update({
_id: projectId,
'en-GB._id': entryId,
}, {
$set: {
'en-GB.$.key': 'g000gle!!',
},
})
.exec();
I've checked that the IDs are correct. But it doesn't update anything:
{ n: 0, nModified: 0, ok: 1 }
What am I doing wrong? Thanks
As discussed in the comments on the question, the issue is directly related to passing in a string representation of an id in the query as opposed to using an ObjectId. In general, it's good practice to treat the use of ObjectIds as the rule and the use of string representations as special exceptions (e.g. in methods like findByIdAndUpdate) in order to avoid this issue.
const { ObjectId } = require('mongodb');
.update({
_id: ObjectId(projectId),
'en-GB._id': ObjectId(entryId),
})
Basically I got my app up an running but I'm stuck with a problem: if I pass an object that contains an empty array to be saved, the array is not saved into the db. I'm not sure this is a problem in js or the mongo driver, but in order to save the empty array I need to pass the array like so: products: [''].
This is the structure of my mongo document:
_id: ObjectId(...),
name: 'String',
subcategories: [
{
subcategory: 'string',
products: [
{
name: 'string'
price: integer
}
]
}
]
So in my front-end I'm grabbing the whole document through an ajax call pushing a new object into the subcategories array. The new object looks like this:
{subcategory:'string', products:['']}
And this works okay until I need to insert a new object inside the array: Because I've grabbed the whole object, pushed the new object to the array, the previous one looks like this:
{subcategory: 'string'}
Having lost the mention to products:[] array in the process.
How can I get around this? I need to be able to have empty arrays in my object.
EDIT
What I did on front end: Got the whole object with $.get which returned:
var obj =
_id: ObjectId(...),
name: 'String',
subcategories: [
{
subcategory: 'Subcategory1',
products: [
{
name: 'string'
price: integer
}
]
}
];
Then on the front end I've pushed the new object category inside the subcategories array:
data.subcategories.push({subcategory: 'Subcategory2', products: ['']})
Where subcat was a string with the category name. On my db I could see that I've successfully added the object:
var obj =
_id: ObjectId(...),
name: 'String',
subcategories: [
{
subcategory: 'Subcategory1',
products: [
{
name: 'string'
price: integer
}
]
},
{
subcategory: 'Subcategory2'
products: []
}
];
The problem was when I wanted to add another subcategory, the previous one return empty:
var obj =
_id: ObjectId(...),
name: 'String',
subcategories: [
{
subcategory: 'Subcategory1',
products: [
{
name: 'string'
price: integer
}
]
},
{
subcategory: 'Subcategory2'
},
{
subcategory: 'Subcategory3'
products: []
},
];
Because at some point the empty array was removed from the object. Like I said, I did fix this in the front end, so the error jade was throwing has been addressed, but I still find odd that the products: [] was being removed from the document.
I'm new to MongoDb and node, not to mention that I'm also new with JS, so it might well be a feature that I'm unaware of.
When passing empty arrays to Mongo they are interpreted as empty documents, {}. Zend Json encoder will interpret them as empty arrays []. I understand that it's not possible to tell which one is correct.
Incase of empty arrays try posting as
Array[null];
instead of Array[];
This will be working fine
When passing empty arrays to Mongo they are interpreted as empty documents, {}. Zend Json encoder will interpret them as empty arrays []. I understand that it's not possible to tell which one is correct.
In my view it's more logical that the actual php array (when empty) is interpreted as an array in MongoDB. Although that will require something else to identify empty documents it's still more logical than the current behaviour.
A possible solution would be to introduce a new object, MongoEmptyObject (or using the stdObj) whenever one want to introduce an empty object.
Meanwhile, a workaround is to detect empty arrays in php, and inject a null value $arr[0] = null;
Then the object will be interpreted as an empty array in mongo.
The workaround works both in PHP and in the mongo console. Question: does json allow for arrays with null values? If so, then the workaround is a sign of another bug.
PHP:
if (is_array($value) && empty($value))
{ $value[0] = null; }
Mongo Console:
var b =
{hej:"da", arr: [null]}
db.test.save(b);
db.test.find();
{"_id" : "4a4b23adde08d50628564b12" , "hej" : "da" , "arr" : []}
I am querying my mongodb using mongoose, but i don't understand why the returned sub docs are just of type Object, instead of JSON.
Using
hero.find({} ,{'deck' : {$elemMatch:{name:'Guard Tower'}}}, function(err, tower) {
console.log(tower);
}
returns
[ { _id: 507ac406ba6ecb1316000001,
deck:
[ { name: 'Guard Tower',
description: 'This tower shoots stuff.',
cost: 13,
sellValue: 7,
radius: 180,
speed: 40,
dmg_min: 0,
dmg_max: 0,
actual_height: 40,
sprite: 'guardtower_red.png',
anim: [Object],
size: [Object],
projectile: [Object],
upgrade: [Object] } ] } ]
Subdocument like anim, size, projectile, upgrade, is Object, i need the information nested, how can i get the information? Without making another query?
The all docs and subdocs are objects in JavaScript. It's just that console.log uses the default depth of 2 when calling util.inspect to format your document for output. You can output all levels of the document by calling util.inspect yourself:
var util = require('util');
hero.find({} ,{'deck' : {$elemMatch:{name:'Guard Tower'}}}, function(err, tower) {
console.log(util.inspect(tower, false, null));
});
JohnnyHK is correct however a simpler approach if you just want to log out JSON would be
console.log(tower.toJSON());
You can see my comment to Rodrigo about why this works.
Making the query on Mongoose using find() as you did will return Mongoose Documents (not JSON). You can use the lean() method to return POJOs:
hero
.find({} ,{'deck' : {$elemMatch:{name:'Guard Tower'}}})
.lean()
.exec(function(err, tower) {
//tower is a JSON here
console.log(tower);
});
But what JohnnyHK said it's true about the console log, it will only show nested documents as [Object].
UPDATE: Beware that using .lean() will return objects and any virtual fields or special getters you might have will be ignored.
I've looked around quite a bit concerning this error, it seems that Mongo won't accept a . or a $ in an update, yet I still get this error
{ [MongoError: not okForStorage]
name: 'MongoError',
err: 'not okForStorage',
code: 12527,
n: 0,
connectionId: 18,
ok: 1 }
This is the object I'm updating:
{
status: "open",
type: "item",
parentId: "4fa13ba2d327ca052d000003",
_id: "4fa13bd6d327ca052d000012",
properties: {
titleInfo: [
{ title: "some item" }
]
}
}
And I'm updating it to:
{
fedoraId: 'aFedoraLib:438',
status: "closed",
type: "item",
parentId: "4fa13ba2d327ca052d000003",
_id: "4fa13bd6d327ca052d000012",
properties: {
titleInfo: [
{ title: "some item" }
]
}
}
Another possible cause I just ran into: storing an object which has periods in the string keys.
So for people getting the same error:
It's due to the fact that I was including the _id, which Mongo doesn't like apparently
I ran into this error when trying to save a JSON structure with this key-value pair (coming straight out of an AngularJS app):
"$$hashKey":"021"
Removing just that key fixed the problem. For others using Angular, it looks like calling Angular's built-in angular.toJson client-side eliminates the $$hashkey keys. From their forums:
$scope.ngObjFixHack = function(ngObj) {
var output;
output = angular.toJson(ngObj);
output = angular.fromJson(output);
return output;
}