How to modify tree object by property? - javascript

I have an object with type:
export interface TreeNode extends ITreeNode {
name: string,
children: TreeNode[],
show?: boolean;
}
I need to reduce this object by property show and return a new tree where show is true or undefined.
I have tried this:
function prepareNodes(source: TreeNode) {
if (source.show!== undefined || source.show == false) delete source;
if (source.children) {
source.children.forEach((child: TreeNode) => {
this.prepareNodes(child);
});
}
}
Also I tried:
function prepareNodes(source: any) {
if (source.show !== undefined && source.show === false) source = null;
if (source.children) {
source.children = source.children.filter((child: any) => child.show== undefined || child.show === true);
source.children.forEach((child: any) => prepareNodes(child));
}
}

Currently my assumption is that you want to produce a new tree containing just those nodes of the original tree where the show property is either true or undefined for that node and all ancestor nodes. So if show is false on any node, the output tree will not contain that node or any subtree of that node.
I also assume that it's possible for the root node to have show of false, in which case the whole tree might end up undefined. You can't set modify an object to become undefined; you can change its contents, but you can't delete it. So I'm not going to present anything that tries to modify the original tree. I won't touch the original tree. Instead I will produce a completely new tree.
Here goes:
const defined = <T,>(x: T | undefined): x is T => typeof x !== "undefined";
function filterTree(source: TreeNode): TreeNode | undefined {
if (source.show === false) return;
return {
name: source.name,
show: source.show,
children: source.children.map(filterTree).filter(defined)
}
}
The filterTree() function will return undefined if the argument node's show property is exactly false (and not undefined). Otherwise, it produces a new node with the same name and show, and whose children property is what you get when you call filterTree() (recursively) on each of the original node's children, and then filter out any undefined nodes.
I'm using a user-defined type guard function called defined to let the compiler know that the filtering takes an array of TreeNode | undefined and produces an array of TreeNode, eliminating any undefined entries.
Hopefully this meets your use cases; please test it out on whatever data you have and check, because the question unfortunately did not include such data.
Playground link to code

Related

What kind of JS data structure internals represent?

I am looking at celebrate.js.
Object internals
internals
{
DEFAULT_ERROR_ARGS: { celebrated: true },
DEFAULT_ERRORS_OPTS: { statusCode: 400 },
DEFAULT_CELEBRATE_OPTS: { mode: 'partial' }
}
Different actions are applied to this object later.
internals.validateSegment = (segment) => (spec, joiConfig) => {
const finalValidate = (req) => spec.validateAsync(req[segment], joiConfig);
finalValidate.segment = segment;
return finalValidate;
};
What do internals represent?
The internals object holds a set of default options (called segments here). They are accessed by their identifiers DEFAULT_ERROR_ARGS, DEFAULT_ERRORS_OPTS and DEFAULT_CELEBRATE_OPTS.
The segments are object themselves, that contain options (e.g. celebrated) and their default values (e.g. true).
All segments of internals can be passed to and validated by validateSegment(). Each property is defined by a key (here: spec) and a value (here: joiConfig). For each specification the function validateAsync() is called and the result is assigned to finalValidate.
The function validateSegment() can be called for individual segments. See function maybeValidateBody() in the code that you linked to, for example:
internals.maybeValidateBody = (segment) => {
const validateOne = internals.validateSegment(segment); <-- Called here!
return (spec, joiConfig) => {
...
In the above code block, the segment is passed to internals.validateSegment() and the return value is assigned to validateOne, for example.

Nested JS decorator get/set's, how to properly chain them?

The ember framework has adopted decorators aggressively. In order to utilize data binding now i have to decorate my properties with #tracked which gets me all my nice UI updates anytime i change a property.
#tracked username = 'dave';
This works well, but i'm encountering some serious problems if i need to add a custom decorator on top of the tracked decorator.
#typed(StateTrackMap)
#tracked
mapConfigsArray = [create(StateTrackMap)];
I'm able to get this to work by having my #typed decorator check to see if it is above another decorator or not.
export default function typed(classType) {
let weak = new WeakMap();
return function(object, property, descriptor) {
return {
get() {
// Check if there is another decorator attached below us in the chain
// i.e. "tracked"
if (typeof descriptor.get == 'function') {
return descriptor.get.call(this);
}
// If we haven't initialized before but there is one ready, return that
if (!weak.has(this) && typeof descriptor.initializer == 'function') {
weak.set(this, descriptor.initializer.call(this));
}
return weak.get(this);
},
set(value) {
// my set code which does the type checking/converting this descriptor is for
// Apply the converted value to the lower level object
// This may be the object itself, or it may be another setter in the chain
if (typeof descriptor.set == 'function') {
descriptor.set.call(this, typedValue);
} else {
return weak.set(this, typedValue);
}
}
}
}
}
But this feels, weird... and doesn't look like any of the usages of descriptors i've seen.
Mostly because if i change the order of the decorators things explode
#tracked
#typed(StateTrackMap)
mapConfigsArray = [create(StateTrackMap)];
index.js:172 Uncaught Error: Assertion Failed: The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both.
So i guess my question is, what is the proper way to chain decorators that have get & set? It seems to me that the order of the decorators determines if i can go up/down the chain or not. Also it seems to me that this chaining logic has to be baked into every decorator or else it doesn't work. Is there some generic way i can pass decorators to other decorators?
I've seen some examples where i return the descriptor reference but that doesn't appear to help the problem here either as i am not quite sure how i can still inject my get/set on it without erasing the property property chain or getting into the same boat as above where my code has to be designed to work with other descriptors specifically.
export default function typed(classType) {
return function(object, property, descriptor) {
const set = descriptor.set;
const get = descriptor.get;
const weak = new WeakMap();
descriptor.get = function() {
if (typeof get == 'function') {
return get.call(this);
}
// If we haven't initialized before but there is one ready, return that
if (!weak.has(this) && typeof descriptor.initializer == 'function') {
weak.set(this, descriptor.initializer.call(this));
}
return weak.get(this);
}
descriptor.set = function(value) {
// My type checking / conversion code
// Apply the converted value to the lower level object
// This may be the object itself, or it may be another setter in the chain
if (typeof set == 'function') {
set.call(this, typedValue);
} else {
return weak.set(this, typedValue);
}
}
return descriptor;
}
}
BTW this method gives a different explosion.
Assertion Failed: You attempted to use #tracked on mapConfigsArray, but that element is not a class field.

Copying React State object; modifying copied object changes state object

I am trying to copy a state object:
#boundMethod
private _onClickDeleteAttachment(attachmentName: string): void {
console.log("_onClickDeleteAttachment | this.state.requestApproval[strings.Attachments]: ", this.state.requestApproval[strings.Attachments]);
let requestApprovalClone = {... this.state.requestApproval}
if (requestApprovalClone === this.state.requestApproval) {
console.log("they are ===");
}
else {
console.log(" they are not ===");
}
_.remove(requestApprovalClone[strings.Attachments], (attachment: any) => {
return attachment.FileName === attachmentName;
})
console.log("_onClickDeleteAttachment | this.state.requestApproval[strings.Attachments]: ", this.state.requestApproval[strings.Attachments]);
console.log("_onClickDeleteAttachment | requestApprovalClone[strings.Attachments]: ", requestApprovalClone[strings.Attachments]);
}
The state object is being altered too. From what I have read, I shouldn't mutate a state object but only change it with setState.
How can I correct this?
You are getting that behavior, because the
let requestApprovalClone = {... this.state.requestApproval}
is only shallow copying the data, your attachments property has some nested objects and it keeps the same reference and therefore when changing it, the cloned object gets altered and the state too.
To avoid that, you can perform another copy of your attachments property like this :
let attachments = [...requestApprovalClone[strings.Attachments]];
_.remove(attachments, function (attachment) {
return attachment.FileName === attachmentName;
});
Changing the attachments variable content won't afftect the state anymore.
you can read more about that behavior here
It has to to with the way that JS handles their const references.
For those who feel adventerous:
let requestApprovalClone = JSON.parse(JSON.stringify(this.state.requestApproval));
// modify requestApprovalClone ...
this.setState({
requestApproval:requestApprovalClone
})
It would be interesting if Object.assign({},this.state.requestApproval); is faster than the whole JSON stringify/parse stuff or vice versa
You can have something like:
let requestApprovalClone = Object.assign({},this.state.requestApproval);
requestApprovalClone.strings.Attachments = requestApprovalClone.strings.Attachments.slice(); // will create a shallow copy of this array as well
_.remove(requestApprovalClone[strings.Attachments], (attachment: any) => {
return attachment.FileName === attachmentName;
})
this.setState({
requestApproval:requestApprovalClone
})// If you want to update that in state back

How do I set "no value" on the server to be compatible with Backbone?

What I mean by this is I want to use the defaults object literal of Models, which calls this code from underscore:
Temp Code
var Feed = Backbone.Model.extend({
defaults: {
name: '',
picture: '', // I need this set when picture is 0
time: '',
tweet: ''
// h_file, view_picture
}
});
From Underscore
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
if (obj[prop] === void 0) obj[prop] = source[prop];
}
}
});
return obj;
};
It will only set a default if a value is set to undefined. However JSON does not support sending undefined, nor do I see this as default value in the mysql table I use.
Obviously, I can use an intermediary value, which is what I'm currently doing. I just store the number 0 as a default for no value and send this via JSON, and then just do a basic if/else.
But this is in-efficient and I would imagine there is a better 1 to 1 correlation I could use.
I would use the built in parse method otherwise I do not see a way around it. It's a difficult predicament, because what would you consider a value that would result in your default value being set? I came up with a quick solution that will filter out ALL values that are considered falsy. You can build on the below.
parse: function(data) {
// This should return values that are truthy
// So for eg if a property has a value of 0 or null it will not be returned,
// thus leaving in your default values
return _.filter(data, function(obj) { return obj; });
}
I found a request for it here as well https://github.com/jashkenas/underscore/issues/157

Mongoose/MongoDB result fields appear undefined in Javascript

Is there something that I'm missing that would allow item to log as an object with a parameter, but when I try to access that parameter, it's undefined?
What I've tried so far:
console.log(item) => { title: "foo", content: "bar" } , that's fine
console.log(typeof item) => object
console.log(item.title) => "undefined"
I'll include some of the context just in case it's relevant to the problem.
var TextController = function(myCollection) {
this.myCollection = myCollection
}
TextController.prototype.list = function(req, res, next) {
this.myCollection.find({}).exec(function(err, doc) {
var set = new Set([])
doc.forEach(function(item) {
console.log(item) // Here item shows the parameter
console.log(item.title) // "undefined"
set.add(item.title)
})
res.json(set.get());
})
}
Based on suggestion I dropped debugger before this line to check what item actually is via the node repl debugger. This is what I found : http://hastebin.com/qatireweni.sm
From this I tried console.log(item._doc.title) and it works just fine.. So, this seems more like a mongoose question now than anything.
There are questions similar to this, but they seem to be related to 'this' accessing of objects or they're trying to get the object outside the scope of the function. In this case, I don't think I'm doing either of those, but inform me if I'm wrong. Thanks
Solution
You can call the toObject method in order to access the fields. For example:
var itemObject = item.toObject();
console.log(itemObject.title); // "foo"
Why
As you point out that the real fields are stored in the _doc field of the document.
But why console.log(item) => { title: "foo", content: "bar" }?
From the source code of mongoose(document.js), we can find that the toString method of Document call the toObject method. So console.log will show fields 'correctly'. The source code is shown below:
var inspect = require('util').inspect;
...
/**
* Helper for console.log
*
* #api public
*/
Document.prototype.inspect = function(options) {
var isPOJO = options &&
utils.getFunctionName(options.constructor) === 'Object';
var opts;
if (isPOJO) {
opts = options;
} else if (this.schema.options.toObject) {
opts = clone(this.schema.options.toObject);
} else {
opts = {};
}
opts.minimize = false;
opts.retainKeyOrder = true;
return this.toObject(opts);
};
/**
* Helper for console.log
*
* #api public
* #method toString
*/
Document.prototype.toString = function() {
return inspect(this.inspect());
};
Make sure that you have defined title in your schema:
var MyCollectionSchema = new mongoose.Schema({
_id: String,
title: String
});
Try performing a for in loop over item and see if you can access values.
for (var k in item) {
console.log(item[k]);
}
If it works, it would mean your keys have some non-printable characters or something like this.
From what you said in the comments, it looks like somehow item is an instance of a String primitive wrapper.
E.g.
var s = new String('test');
typeof s; //object
s instanceof String; //true
To verify this theory, try this:
eval('(' + item + ')').title;
It could also be that item is an object that has a toString method that displays what you see.
EDIT: To identify these issues quickly, you can use console.dir instead of console.log, since it display an interactive list of the object properties. You can also but a breakpoint and add a watch.
Use findOne() instead of find().
The find() method returns an array of values, even if you have only one possible result, you'll need to use item[0] to get it.
The findOne method returns one object or none, then you'll be able to access its properties with no issues.
Old question, but since I had a problem with this too, I'll answer it.
This probably happened because you're using find() instead of findOne(). So in the end, you're calling a method for an array of documents instead of a document, resulting in finding an array and not a single document. Using findOne() will let you get access the object normally.
A better way to tackle an issue like this is using doc.toObject() like this
doc.toObject({ getters: true })
other options include:
getters: apply all getters (path and virtual getters)
virtuals: apply virtual getters (can override getters option)
minimize: remove empty objects (defaults to true)
transform: a transform function to apply to the resulting document before returning
depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
versionKey: whether to include the version key (defaults to true)
so for example you can say
Model.findOne().exec((err, doc) => {
if (!err) {
doc.toObject({ getters: true })
console.log('doc _id:', doc._id) // or title
}
})
and now it will work
You don't have whitespace or funny characters in ' title', do you? They can be defined if you've quoted identifiers into the object/map definition. For example:
var problem = {
' title': 'Foo',
'content': 'Bar'
};
That might cause console.log(item) to display similar to what you're expecting, but cause your undefined problem when you access the title property without it's preceding space.
I think using 'find' method returns an array of Documents.I tried this and I was able to print the title
for (var i = 0; i < doc.length; i++) {
console.log("iteration " + i);
console.log('ID:' + docs[i]._id);
console.log(docs[i].title);
}
If you only want to get the info without all mongoose benefits, save i.e., you can use .lean() in your query. It will get your info quicker and you'll can use it as an object directly.
https://mongoosejs.com/docs/api.html#query_Query-lean
As says in docs, this is the best to read-only scenarios.
Are you initializing your object?
function MyObject()
{
this.Title = "";
this.Content = "";
}
var myo1 = new MyObject();
If you do not initialize or have not set a title. You will get undefined.
When you make tue query, use .lean() E.g
const order = await Order.findId("84578437").lean()
find returns an array of object , so to access element use indexing, like
doc[0].title

Categories

Resources