Related
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.
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
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"')
I have a MySQL Database which I need to query from node.js
I am using bookshelf and knex for this.
I want to get the contents of a table - I have defined a table in my model.js file. I am attempting the query like this:
//select * from completedSentences;
Model.CompletedSentences.fetchAll().then(function (resData) {
console.log(resData)
})
I would like to know how to loop over resData because it should be multiple rows.
The output of the console looks like this: I dont see a list of rows I can loop over.. What am i missing?
CollectionBase {
model:
{ [Function]
NotFoundError: [Function: ErrorCtor],
NoRowsUpdatedError: [Function: ErrorCtor],
NoRowsDeletedError: [Function: ErrorCtor] },
length: 1,
models:
[ ModelBase {
attributes: [Object],
_previousAttributes: [Object],
changed: {},
relations: {},
cid: 'c4',
id: 1 } ],
_byId:
{ '1':
ModelBase {
attributes: [Object],
_previousAttributes: [Object],
changed: {},
relations: {},
cid: 'c4',
id: 1 },
c4:
ModelBase {
attributes: [Object],
_previousAttributes: [Object],
changed: {},
relations: {},
cid: 'c4',
id: 1 } },
_knex: null,
_events: {},
_eventsCount: 0 }
I found the answer (the documentation is very cryptic, hope this helps others)
new Model.CompletedSentences().fetchAll().then(function (resData) {
_.each(resData.models, function (model) { //I am looping over models using underscore, you can use any loop
console.log(model.attributes)
})
})
Model.CompletedSentences.fetchAll().then(function (resData) {
console.log(resData.serialize())
})
output is in json format
http://bookshelfjs.org/#Model-instance-serialize
The Collection class has a set of lodash methods for this.
You can use collection.forEach this way:
new Model.CompletedSentences().fetchAll().then(function (completedSentences) {
completedSentences.forEach(function (model) {
console.log(model.attributes)
})
})
Check out the docs, there are many other useful methods for Collection.
If you dont want to use lodash, you can do this:
new Model.CompletedSentences().fetchAll().then(function (resData) {
resData.models.forEach( function (model) {
console.log(model.get('attribute');
console.log(model.related('sth').get('attribute');
})
})
simply just console with it attributes.
console.log(resData.attributes);
you will get results Objects vise.
Answer is simple, try this
console.log(resData.toJSON())
I'm attempting to iterate over fields in a mongoose model within a middleware function. The current context this is the model object itself. So I have a function in which the context is a Javascript object like this:
{
lastName: 'Wilkens',
firstName: 'Elepart',
name: 'Elepart Wilkens',
username: 'eK',
bio: '<script>alert(\'this is a hack!!\')',
_id: 53b17dd0e8c5af50c1d73bc6,
language: 'en',
country: 'USA',
online: true
}
I want to iterate over this object (which is represented in the current function with this). Every time I attempt to iterate with loops, it prints out values that look like internal Javascript metadata. Is it possible to iterate over this within a function if this represents an object?
Here's the actual middleware function:
userSchema.pre('save', function (next) {
console.log(this); // This prints precisely the same object I have copied above
var fields = Object.keys(this);
for(var i = 0; i < fields.length; i++) {
console.log(this[fields[i]]);
}
for(var key in this) {
if(this.hasOwnProperty(key)) {
console.log(this[key]);
}
}
});
And the output is:
{
strictMode: true,
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:
{ username: 'modify',
firstName: 'modify',
name: 'modify',
online: 'default',
country: 'default',
language: 'default',
_id: 'default',
bio: 'modify',
lastName: 'modify' },
states: { default: [Object], init: {}, modify: [Object], require: {} },
stateNames: [ 'require', 'modify', 'init', 'default' ],
map: [Function] },
ownerDocument: undefined,
fullPath: undefined }
true
undefined
0
{ online: true,
country: 'USA',
language: 'en',
_id: 53b1825a00ed9af7c12eedf9,
bio: '<script>alert(\'this is a hack!!\')',
username: 'yK',
name: 'Yershay Wilkens',
firstName: 'Yershay',
lastName: 'Wilkens' }
{ save:
[ { [Function] isAsync: false },
{ [Function: checkForExistingErrors] isAsync: false },
{ [Function: validation] isAsync: false },
{ [Function] isAsync: false },
{ [Function] isAsync: false } ],
validate: [ { [Function] isAsync: false } ] }
{ save: [], validate: [] }
{ [Function] numAsyncPres: 0 }
{ [Function] numAsyncPres: 0 }
{}
You're using integer indexes instead of the string references from the fields array. It should be:
var fields = Object.keys(this);
for(var i = 0; i < fields.length; i++) {
console.log(this[fields[i]]);
}
(e.g., you were doing this[1], this[2], instead of this[fields[1]])
#JohnnyHK's comment worked for me:
const user = new User();
const schemaKeys = Object.keys(user.toObject());
I found a slightly different way to accomplish what I wanted which was to iterate over the model properties of a Mongoose model within a middleware function. This uses async.js, but you could refactor it to use a generic JS loop or any other control flow library. The key is to get an array of the document's fields, then you can iterate over those and get/set the values using the current context with this. As far as I know, this will not coerce non-string values into strings. I've tested it with strings, numbers, booleans and objectIds and they are successfully saved as their original data types.
yourSchema.pre('save', function (next) {
var self = this;
// Get the document's fields
var fields = Object.keys(this._doc);
// Do whatever you want for each field
async.each(fields, function(field, cb) {
self[field] = validator.escape(self[field]);
cb();
}, function(err){
next();
});
});