Node JS [Object Map] while writing object to file - javascript

I read many posts on stackoverflow and other tutorial sites for writing object/JSON to a file, but none of the solutions worked for me.
1) Code:
let messages = await fetchMessages()
console.log(messages) // Prints an object
fs.writeFileSync('./msgdata.json', messages , 'utf-8');
2) Also tried,
fs.writeFileSync('./msgdata.json', JSON.stringify(messages) , 'utf-8');
3) Also tried,
fs.writeFile(), but get same output as above.
msgdata.json: (For 1)
[object Map]
msgdata.json: (For 2 & 3)
{}
Can someone please point out what could be causing this?
## Output of console.log(messages) ##
OUTPUT contains 10 more such objects having different id's:
channel:
TextChannel {
type: 'text',
id: '424825532274253312',
name: 'request',
position: 69,
parentID: '397363224286473987',
permissionOverwrites: [Object],
topic: null,
nsfw: false,
lastMessageID: '427143105410760704',
guild: [Object],
messages: [Object],
_typing: Map {} },
id: '427142596817846272',
type: 'DEFAULT',
content: '**Select your emoji:** __***Group 9:***__',
author:
ClientUser {
id: '407083773537350272',
username: 'Bot',
discriminator: '9256',
avatar: '9c374e719ba2ab4e69fd577005b635bf',
bot: true,
lastMessageID: null,
lastMessage: null,
verified: true,
email: null,
localPresence: {},
_typing: Map {},
friends: Collection {},
blocked: Collection {},
notes: Collection {},
premium: null,
mfaEnabled: false,
mobile: null,
settings: [Object],
guildSettings: Collection {} },
member:
GuildMember {
guild: [Object],
user: [Object],
_roles: [Array],
serverDeaf: false,
serverMute: false,
selfMute: undefined,
selfDeaf: undefined,
voiceSessionID: undefined,
voiceChannelID: undefined,
speaking: false,
nickname: null,
joinedTimestamp: 1517127343434,
lastMessageID: null,
lastMessage: null },
pinned: false,
tts: false,
nonce: undefined,
system: false,
embeds: [],
attachments: Collection {},
createdTimestamp: 1521909131007,
editedTimestamp: null,
reactions:
Collection {
'taillow:417281639777959940' => [Object],
'shroomish:417281639899463680' => [Object],
'sableye:417281640197521419' => [Object],
'ralts:417281642735075329' => [Object],
'sentret:417281644001624076' => [Object],
'shuppet:417281644291162132' => [Object],
'torchic:417281647210397706' => [Object],
'snubbull:417281647692480522' => [Object],
'sunkern:417281647763783681' => [Object],
'slowpoke:417281648653107200' => [Object],
'teddiursa:417281649537974273' => [Object],
'sneasel:417281649613471747' => [Object],
'snorunt:417281649819123712' => [Object],
'surskit:417281650163056640' => [Object],
'qwilfish:417281654629859348' => [Object],
'shelgon:417281654730522624' => [Object] },
mentions:
MessageMentions {
everyone: false,
users: Collection {},
roles: Collection {},
_content: '**Select your emoji:** __***Group
9:***__',
_client: [Object],
_guild: [Object],
_members: null,
_channels: null },
webhookID: null,
hit: null,
_edits: [] },

This is because the object returned is a Map object (MDN). When you JSON.strigify a Map object, it always returns {}. There are two ways to get readable JSON that stores your map. The first is to loop over all the entries and create a JSON (taking in consideration the keys and values). That should be simple to achieve.
Another way is to create an array from the Map and then stringify it.
fs.writeFileSync('./msgdata.json', JSON.stringify([...myObject]) , 'utf-8');

You can define Map.prototype.toJSON which works, but it isn't standard and is discouraged. It is automatically called by JSON.stringify if it finds it.
Map.prototype.toJSON = function() {return [...this];};
console.log(JSON.stringify(messages));
A better way is to define your own replacer function (and reviver function too if you need to reconstruct map from JSON). Here I also wrap the map representing array in a special object to tell maps and ordinary arrays apart in JSON:
function replacer (key, value) {
if (value instanceof Map) {
return {
_type: "map",
map: [...value],
}
} else return value;
}
function reviver (key, value) {
if (value._type == "map") return new Map(value.map);
else return value;
}
let str = JSON.stringify(messages, replacer);
let msg = JSON.parse(str, reviver);
The error you are getting about circular structure means that some objects/arrays there contain references to itself. When you store messages you don't need to store all the information related to the channels etc and instead only store ids. So that means extending replacer for TextChannels, ClientUsers, GuildMembers etc:
if (value instanceof TextChannel) {
return {
_type: "TextChannel",
id: value.id,
};
}

var util = require('util');
fs.writeFileSync('./data.json', util.inspect(obj) , 'utf-8');
Ref. Write objects into file with Node.js

Related

Recursive in typescript

Please help me write a method for finding child objects. The method receives the name of the object and an array of all possible objects. I need to find an object by the input name in this spike, then see what child objects it has, then go through each object in the same way. Thus, you need to find all the children throughout the cascade. The resulting file is this.ArchiveObject[]. The method I wrote "findChildObjects" goes to infinity and doesn't work correctly.
public async execute(): Promise<void> {
const templateExecutionSteps = await this.getTemplateExecutionSteps();
// Get global describe
const globalDescribe = await SfDescribe.objects(this.auth);
await this.backup.sobjectKeyPrefix(globalDescribe);
// Get names of user-selected objects
const objectsName = [...templateExecutionSteps.keys()].map(objectName => {
const describeObject = globalDescribe.sobjects.find(describeObj => describeObj.name === objectName);
if (!describeObject) throw new Error(`Object ${objectName} not found`);
if (!describeObject.queryable) throw new Error(`Object ${objectName} not queryable`);
return describeObject;
});
// Get names of all objects from org
const globalDescribeNames: string[] = [];
globalDescribe.sobjects.forEach(obj => globalDescribeNames.push(obj.name));
// Get global describe for all objects from org
const describeAllObjects = await SfCompositeUtils.describeObjects(this.auth, globalDescribeNames, true);
// Push global describe for all objects result in map
const globalDescribeMap: Map<string, SFDescribeObjects> = new Map();
describeAllObjects.forEach(objectDescribe => globalDescribeMap.set(objectDescribe.name, objectDescribe));
this.objectsArray = [];
objectsName.forEach(object => {
const allObjects = this.findChildObjects(object.name, globalDescribeMap);
});
}
public findChildObjects(selectedObject: string, globalDescribeMap: Map<string, SFDescribeObjects>): any {
const currentObject: ArchiveObject = {};
console.log(globalDescribeMap);
globalDescribeMap.get(selectedObject).childRelationships.forEach(object => {
if (
this.objectsArray.find(item => item.objectName === object.childSObject && item.referenceField === object.field)
) {
return;
}
currentObject.referenceTo = selectedObject;
currentObject.objectName = object.childSObject;
currentObject.referenceField = object.field;
if (object.cascadeDelete || object.restrictedDelete) {
currentObject.isDelete = true;
this.objectsArray.push(currentObject);
const result = this.findChildObjects(object.childSObject, globalDescribeMap);
this.objectsArray.push(...result);
} else {
currentObject.isDelete = false;
this.objectsArray.push(currentObject);
}
});
return this.objectsArray;
}
export interface ArchiveObject {objectName?: string; referenceField?: string; referenceTo?: string; isDelete?: boolean;}
Here is the globalDescribeMap structure:
'DashboardFeed' => {
actionOverrides: [],
activateable: false,
associateEntityType: 'Feed',
associateParentEntity: 'Dashboard',
childRelationships: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object]
],
compactLayoutable: false,
createable: false,
custom: false,
customSetting: false,
deepCloneable: false,
defaultImplementation: null,
deletable: true,
deprecatedAndHidden: false,
extendedBy: null,
extendsInterfaces: null,
feedEnabled: false,
fields: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object]
],
hasSubtypes: false,
implementedBy: null,
implementsInterfaces: null,
isInterface: false,
isSubtype: false,
keyPrefix: null,
label: 'Dashboard Feed',
labelPlural: 'Dashboard Feed',
layoutable: false,
listviewable: null,
lookupLayoutable: null,
mergeable: false,
mruEnabled: false,
name: 'DashboardFeed',
namedLayoutInfos: [],
networkScopeFieldName: null,
queryable: true,
recordTypeInfos: [],
replicateable: true,
retrieveable: true,
searchLayoutable: false,
searchable: false,
sobjectDescribeOption: 'FULL',
supportedScopes: [ [Object] ],
triggerable: false,
undeletable: false,
updateable: false,
urls: {
rowTemplate: '/services/data/v50.0/sobjects/DashboardFeed/{ID}',
describe: '/services/data/v50.0/sobjects/DashboardFeed/describe',
sobject: '/services/data/v50.0/sobjects/DashboardFeed'
}
},
Visual structure of one object
I think the problem could be that there are circular dependencies.
Your data model can appear to be a tree, while it is actually more like a graph. If you apply recursive search algorithms to traverse through a graph, you can get stuck in an endless loop. Due to the circular references, it would be like searching in a tree of endless depth.
The solution, is to never process an element twice. To accomplish this, you need to keep track of a collection of processed elements, and check whether they were already processed before.
Your findChildObjects function could use a Set to mark/verify that objects have been processed.
public findChildObjects(selectedObject,..., alreadyProcessed: Set) {
// make sure objects are processed only once
if (alreadyProcessed.has(selectedObject)) return [];
alreadyProcessed.add(selectedObject);
...
}
Of course, you would need to create the set in advance before calling it the first time.
const alreadyProcessed = new Set();
...
this.findChildObjects(..., ..., alreadyProcessed);
Basically, whenever you call the this.findChildObjects(...) you pass along the collection, which makes it possible to detect and skip objects that already have been processed.

Unwanted objects added on my mongodb response [duplicate]

I have defined a Model with mongoose like this:
var mongoose = require("mongoose")
var Schema = mongoose.Schema
var userObject = Object.create({
alias: String,
email: String,
password: String,
updated: {
type: Date,
default: Date.now
}
})
var userSchema = new Schema(userObject, {strict: false})
var User = mongoose.model('User', userSchema)
module.exports = User
Then I created a user that I can perfectly find through mongo console like this:
db.users.findOne({ email: "coco#coco.com" });
{
"_id" : ObjectId("55e97420d82ebdea3497afc7"),
"password" : "caff3a46ebe640e5b4175a26f11105bf7e18be76",
"gravatar" : "a4bfba4352aeadf620acb1468337fa49",
"email" : "coco#coco.com",
"alias" : "coco",
"updated" : ISODate("2015-09-04T10:36:16.059Z"),
"apps" : [ ],
"__v" : 0
}
However, when I try to access this object through a node.js with mongoose, the object a retrieve is not such doc, but a wrapper:
This piece of code...
// Find the user for which the login queries
var User = require('../models/User')
User.findOne({ email: mail }, function(err, doc) {
if (err) throw err
if (doc) {
console.dir(doc)
if(doc.password == pass) // Passwords won't match
Produces this output from console.dir(doc)...
{ '$__':
{ strictMode: false,
selected: undefined,
shardval: undefined,
saveError: undefined,
validationError: undefined,
adhocPaths: undefined,
removing: undefined,
inserting: undefined,
version: undefined,
getters: {},
_id: undefined,
populate: undefined,
populated: undefined,
wasPopulated: false,
scope: undefined,
activePaths: { paths: [Object], states: [Object], stateNames: [Object] },
ownerDocument: undefined,
fullPath: undefined,
emitter: { domain: null, _events: {}, _maxListeners: 0 } },
isNew: false,
errors: undefined,
_doc:
{ __v: 0,
apps: [],
updated: Fri Sep 04 2015 12:36:16 GMT+0200 (CEST),
alias: 'coco',
email: 'coco#coco.com',
gravatar: 'a4bfba4352aeadf620acb1468337fa49',
password: 'caff3a46ebe640e5b4175a26f11105bf7e18be76',
_id: { _bsontype: 'ObjectID', id: 'Uét Ø.½ê4¯Ç' } },
'$__original_validate': { [Function] numAsyncPres: 0 },
validate: [Function: wrappedPointCut],
_pres: { '$__original_validate': [ [Object] ] },
_posts: { '$__original_validate': [] } }
Therefore, passwords won't match because doc.password is undefined.
Why is this caused?
That's exactly the purpose of mongoose, wrapping mongo objects. It's what provides the ability to call mongoose methods on your documents. If you'd like the simple object, you can call .toObject() or use a lean query if you don't plan on using any mongoose magic on it at all. That being said, the equality check should still hold as doc.password returns doc._doc.password.

Getting the same Object back when using the array.filter with a valid condition

I am trying to get the value of the 'text' key in a Json object that I get from a GET request to twitter via the twit library.
Im trying to use the filter function on the array because i wanna do functional programming.
I can use the json object inside a callback function on the T.get function:
const gotData = function(err, data, response) {
console.log(data)
}
T.get('search/tweets', params , gotData)
Then I want to filter only the value on the text key on each of the statuses, which is an array of objects
So i tried this:
let results = data.statuses.filter(
function(result){ return result.hasOwnProperty('text')
})
But I get back the same data.statuses back... ?
I want to get only the value of the text keys on the objects, what im I doing wrong ?
this is my whole code:
const Twit = require('twit')
const config = require('./config')
const T = new Twit(config)
let params = { q: 'drum', count: 2 }
const gotData = function(err, data, response) {
let results = data.statuses.filter(function(result){ return result.hasOwnProperty('text')})
console.log(results)
}
T.get('search/tweets', params , gotData)
And this a sample of the Json response i get, which corresponds to the data on the gotData function:
{ statuses:
[ { created_at: 'Sun May 07 11:35:16 +0000 2017',
id: 861182642888343600,
id_str: '861182642888343552',
text: '#was_going \nEvery hunk bore seven drums,\nEvery drum owned seven drums…',
truncated: false,
entities: [Object],
metadata: [Object],
source: 'Cheap Bots, Done Quick!',
in_reply_to_status_id: 861181362954489900,
in_reply_to_status_id_str: '861181362954489856',
in_reply_to_user_id: 795028044758949900,
in_reply_to_user_id_str: '795028044758949888',
in_reply_to_screen_name: 'was_going',
user: [Object],
geo: null,
coordinates: null,
place: null,
contributors: null,
is_quote_status: false,
retweet_count: 0,
favorite_count: 0,
favorited: false,
retweeted: false,
lang: 'en' },
{ created_at: 'Sun May 07 11:35:09 +0000 2017',
id: 861182610596405200,
id_str: '861182610596405248',
text: 'Would you rather have Data storage or A pedal assembly for a bass drum or high hat cymbals?',
truncated: false,
entities: [Object],
metadata: [Object],
source: 'Cheap Bots, Done Quick!',
in_reply_to_status_id: null,
in_reply_to_status_id_str: null,
in_reply_to_user_id: null,
in_reply_to_user_id_str: null,
in_reply_to_screen_name: null,
user: [Object],
geo: null,
coordinates: null,
place: null,
contributors: null,
is_quote_status: false,
retweet_count: 0,
favorite_count: 0,
favorited: false,
retweeted: false,
lang: 'en' } ],
search_metadata:
{ completed_in: 0.034,
max_id: 861182642888343600,
max_id_str: '861182642888343552',
next_results: '?max_id=861182610596405247&q=drum&count=2&include_entities=1',
query: 'drum',
refresh_url: '?since_id=861182642888343552&q=drum&include_entities=1',
count: 2,
since_id: 0,
since_id_str: '0' } }
You just check if theres a text property. You might also want to map it to that property:
let results = data.statuses.filter(
result=>result.hasOwnProperty('text')
).map(result=>result.text);
http://jsbin.com/niwezocuhu/edit?console
Or if you take care on performance ( O(n) instead of O(n+n*r)):
let results= data.statuses.reduce((arr,result)=>(result.text&&arr.push(result.text)&&false)||arr,[]);

Node JS - pull VAR out of array

I trying to scrape data off a website. What is returned to me is an array that looks like.
var $ = cheerio.load(body)
returns:
{ [Function]
fn:
{ constructor: [Circular],
_originalRoot:
{ type: 'root',
name: 'root',
attribs: {},
children: [Object],
next: null,
prev: null,
parent: null } },
load: [Function],
html: [Function],
xml: [Function],
text: [Function],
parseHTML: [Function],
root: [Function],
contains: [Function],
The child object is what I'm after so...
var obj = $.root()[0]
returns:
{ type: 'root',
name: 'root',
attribs: {},
children:
[ { data: 'var usdata_real = { // All_data node opens\n\n\tATL: { // Country tag opens\n\t\tid: "ATL",\n\t\t\tdata: { // Data node begins\n\t\t\tq1_1980: 169.304888373489,\n\t\t\tq2_1980: 164.585093140904,\n\t\t\tq3_1980: 166.167521028595,\n\t\t\tq4_1980: 163.
....
I want the 'data' in this array.
obj = obj['children'][0]['data']
returns:
var usdata_real = { // All_data node opens
ATL: { // Country tag opens
id: "ATL",
data: { // Data node begins
q1_1980: 169.304888373489,
q2_1980: 164.585093140904,
q3_1980: 166.167521028595,
q4_1980: 163.040844286677,
q1_1981: 163.31019903107,
q2_1981: 162.449966027231,
q3_1981: 158.719442789479,
q4_1981: 162.744884934737,
.....
which is ALMOST perfect. Except it returns to me an array which is no bueno. I want to be able treat this as an object so i can do something like
obj['ATL']
and get they KEY/VALUE pairs.
So my question....
Is it possible to strip out the object in this array ('var usdata_real'). Because that is what I'm after.
Any tips would be greatly appreciated.
Thanks!

Javascript: how to loop and change key name?

How can I change key name but key its values?
For instance I have this json data that I have stored:
{ particles:
{ name: 'particles',
values: [ [Object], [Object], [Object], [Object], [Object] ] },
timestamps:
{ name: 'timestamps',
values: [ [Object], [Object], [Object], [Object], [Object] ] }
}
And I will loop this input and change the key:
{ particles: 'particle', timestamps: 'timestamp' }
Change particles to particle and timestamps to timestamp
My attemp:
for (var property in data) {
stored[data[property]] = stored[property].values;
stored[property].name = data[property];
}
I only managed to change the name's value inside the stored data but not the key name...
Any ideas?
Assign new property by getting old property value and then delete old property.
var data = {
particles: {
name: 'particles',
values: []
},
timestamps: {
name: 'timestamps',
values: []
}
}
var newK = {
particles: 'particle',
timestamps: 'timestamp'
};
// get all object keys and iterate over them
Object.keys(newK).forEach(function(ele) {
// assign object property based on old property value
data[newK[ele]] = data[ele];
// update name property
data[newK[ele]].name = newK[ele];
// delete old object property
delete data[ele];
})
console.log(data);
You can achieve it by iterating the data and create the new keys and delete the old ones.
E.g.
var data = {
particles: {
name: 'particles',
values: [ [Object], [Object], [Object], [Object], [Object] ]
},
timestamps: {
name: 'timestamps',
values: [ [Object], [Object], [Object], [Object], [Object] ]
}
};
var res = { particles: 'particle', timestamps: 'timestamp' };
for (var k in res) {
var newValue = res[k];
data[newValue] = data[k];
data[newValue].name = newValue;
delete data[k];
}
You first have to remove the key using the delete statement after that you can add the new property using the Static method of the Object class using the defineProperty method. Here it is sample code.
var data={ particles: 'particle', timestamps: 'timestamp' };
for(var k in data){
document.write(k+":"+data[k]+"<br/>");
}
if(data.hasOwnProperty("particles")){
value=data["particles"];
delete data["particles"];
Object.defineProperty(data,"particle",{
value: value,
writable: true,
enumerable: true,
configurable: true,
});
}
if(data.hasOwnProperty("timestamps")){
value=data["timestamps"];
delete data["timestamps"];
Object.defineProperty(data,"timestamp",{
value: value,
writable: true,
enumerable: true,
configurable: true,
});
}
document.write("<br/>new values <br/>");
for(var k in data){
document.write(k+":"+data[k]+"<br/>");
}
Happy Coding!!!
:)
You can convert your Object to a String using JSON.stringify, then replace any occurrences of particles or timestamps (str.replace(/particles/g, "particle")). Finally, convert your string back to an Object using JSON.parse(str).
NB: to make sure that you will not alter any other data but the keys:
str.replace(/{"particles":{"name":"particles"/g, '{"particle":{"name":"particle"')

Categories

Resources