Unshift from object list without using delete? - javascript

If I can access an object from an object using list[value][index], how can I delete or unshift that object from list without using delete? (since that isn't possible in an object list)
My object looks like this:
var list = {
'test1': [
{
example1: 'hello1'
},
{
example2: 'world1'
}
]
'test2': [
{
example1: 'hello2'
},
{
example2: 'world2'
}
]
};
After deleting an object, I want it to look like this:
var list = {
'test1': [
{
example1: 'hello1'
}
]
'test2': [
{
example1: 'hello2'
},
{
example2: 'world2'
}
]
};
When I use delete, it looks like this:
var list = {
'test1': [
{
example1: 'hello1'
},
null
]
'test2': [
{
example1: 'hello2'
},
{
example2: 'world2'
}
]
};

You can remove the object from list by setting the value of list[key] to undefined. This won't remove the key, however - you'd need delete to do that:
list['test1'] = undefined; // list is now { test1: undefined, test2: [ ... ]}
delete list['test1']; // list is now { test2: [ ... ] }
Is there a particular reason you don't want to use delete? It won't make a difference if you're just checking list['test1'] for truthiness (e.g. if (list['test1']) ...), but if you want to iterate through list using for (var key in list) or something like that, delete is a better option.
EDIT: Ok, it looks like your actual question is "How can I remove a value from an array?", since that's what you're doing - the fact that your array is within an object, or contains objects rather than other values, is irrelevant. To do this, use the splice() method:
list.test1.splice(1,1); // list.test1 has been modified in-place

(Rewritten for corrected question.)
You can write:
list[value].splice(index, 1);
to delete list[value][index] and thereby shorten the array by one. (The above "replaces" 1 element, starting at position index, with no elements; see splice in MDN for general documentation on the method.)

Here is an example using splice:
http://jsfiddle.net/hellslam/SeW3d/

Related

Typescript - Complex Object merge with Dot Notation

I want to display the data in a Tree View in Angular and need to transform an array of dot-notated elements into a collection of objects with children.
This is the array I'm working with. Notice the key field in every element.
So the structure I need is for example (for the first 4 elements in the array):
const data = [
{
key: 'bs',
children: [
{
key: 'ass',
children: [
{
key: 'fixAss',
decimals: '0',
unitRef: 'unit_euro',
contextRef: 'period_2019',
value: 15542000,
children: [
{
key: 'intan',
decimals: '0',
unitRef: 'unit_euro',
contextRef: 'period_2019',
value: 8536000,
children: [
{
key: 'concessionBrands',
decimals: '0',
unitRef: 'unit_euro',
contextRef: 'period_2019',
value: 8536000,
children: [] // If there are no children in the element this can be empty or left out
}
]
},
{
key: 'tan',
decimals: '0',
unitRef: 'unit_euro',
contextRef: 'period_2019',
value: 6890000,
children: []
}
]
}
]
}
]
}
];
That means elements are combined by having a key attribute which holds the notation for that level (i.e "bs", "ass", "fixAss", ...) and then children of the next level. An element can have values of its own ("decimals", "unitRef",...) and might additionally also have children that are made up the same way. There is no restriction on the amount of levels this can have.
I have the lodash and dot object libraries in my package.json. Any help is very much appreciated.
it seems the dot-object lib has no things to work with something like "children" that you have, so it seems custom code is required to build what you expected
// balanceData got somehow
let data = [];
const getOrBuildPathObject = (path) => {
let currentLevel = data;
let obj = null;
for(let keyFragment of path.split('.')) {
obj = currentLevel.find(v => v.key == keyFragment);
if(!obj) {
obj = {key: keyFragment, children: []};
currentLevel.push(obj);
}
currentLevel = obj.children;
}
return obj;
}
balanceData.forEach((d) => {
let {key, ...data} = d;
Object.assign(getOrBuildPathObject(key), data);
})
should be something like that
I would just iterate through the array and check each key.
Split the key at the dots myArray.split('.') returns an array.
Now iterate through that array and create an Object for each element.
Like
bs.ass.fixAss
Check if a root element bs exists.
If no, create an (empty) bs Element.
Check if an ass element is a child of bs
If no, create an (empty) ass Element
Check if an (empty) fixAss Element exists.
If no, create the fixAss Element with values and add it as child to the ass Element
If yes, fill the values
If its guaranteed that the data will always be in the right order (that means bs.ass.fixAss will always be AFTER bs.ass) then you may skip the checks.
I would use a HashMap for the children (not an array), because that makes it much easier to walk through the tree
myTrees[bs].children[ass].children[fixAss]
The whole thing could be created with plain TypesScript. I do not know any library that would solve this specific problem out of the box.

Object spread inside of new array

I have a Node.js program that is using Mongo Atlas search indexes and is utilizing the Aggregate function inside of the MongoDB driver. In order to search, the user would pass the search queries inside of the query parameters of the URL. That being said, I am trying to build a search object based on if a query parameter exists or not. In order to build the search object I am currently using object spread syntax and parameter short-circuiting, like so:
const mustObj = {
...(query.term && {
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
})
}
This is a shortened version, as there are many more parameters, but you get the jest.
In a MongoDB search query, if you have multiple parameters that must meet a certain criteria, they have to be included inside of an array called must, like so:
{
$search: {
compound: {
must: [],
},
},
}
So, in order to include my search params I must first turn my mustObj into an array of objects using Object.keys and mapping them to an array, then assigning the searches 'must' array to the array I've created, like so:
const mustArr = Object.keys(mustObj).map((key) => {
return { [key === 'text2' ? 'text' : key]: mustObj[key] };
});
searchObj[0].$search.compound.must = mustArr;
What I would like to do is, instead of creating the mustObj and then looping over the entire thing to create an array, is to just create the array using the spread syntax and short-curcuiting method I used when creating the object.
I've tried the below code, but to no avail. I get the 'object is not iterable' error:
const mustArr = [
...(query.term && {
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
})
]
In all, my question is, is what I'm asking even possible? And if so, how?
Corrected based on #VLAZ comment:
while spread with array [...(item)], item has to be array (iterable).
When you use short-circuit, the item as below,
true && [] ==> will be `[]` ==> it will work
false && [] ==> will be `false` ==> wont work (because false is not array)
try some thing like (Similar to #Chau's suggestion)
const mustArr = [
...(query.term ? [{
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
}] : [])
]

javascript recursive function concat not working

I have a nested array of objects like below and I'm trying to push all the values in to a single array. all the values are located in sp->it->value or sp->it->it->value
[
{
"sp": [
{
"it":[
{"value":5}
]
},
...
],
"b": {
...
}
},
{
"sp": [
{
"it":[
{"nm":5}
]
}
],
"b": {
...
}
},
{
"sp": [
{
"it":[
{
"it":[
{"value":5}
]
}
]
}
],
"b": {
...
}
},
]
and here is what I have tried
const getValues = (js) => {
let values = []
js.map((val,i) => {
if("sp" in val) values.concat(getValues(val.sp))
else if("it" in val) values.concat(getValues(val.it))
else if("value" in val) values.push(val.value)
})
return values
}
I thought I could concatenate the returned value from the recursive call since it returns an array but the above code returns empty array. Any insights?
Edit fixed the typo on sp object. It is array of objects.
Array.prototype.concat()
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
So those lines do nothing:
if("sp" in val) values.concat(getValues(val.sp))
else if("it" in val) values.concat(getValues(val.it))
You need to write:
if("sp" in val) values = values.concat(getValues(val.sp))
else if("it" in val) values = values.concat(getValues(val.it))
And you should not use map if you don't use it's result. Use forEach instead.
This is because you are passing val.sp to function which is not array but it is an object and .map is a property of an array

Vue computed property changes the data object

I have basically this structure for my data (this.terms):
{
name: 'First Category',
posts: [
{
name: 'Jim James',
tags: [
'nice', 'friendly'
]
},
{
name: 'Bob Ross',
tags: [
'nice', 'talkative'
]
}
]
},
{
name: 'Second Category',
posts: [
{
name: 'Snake Pliskin',
tags: [
'mean', 'hungry'
]
},
{
name: 'Hugo Weaving',
tags: [
'mean', 'angry'
]
}
]
}
I then output computed results so people can filter this.terms by tags.
computed: {
filteredTerms: function() {
let self = this;
let terms = this.terms; // copy original data to new var
if(this.search.tags) {
return terms.filter((term) => {
let updated_term = {}; // copy term to new empty object: This doesn't actually help or fix the problem, but I left it here to show what I've tried.
updated_term = term;
let updated_posts = term.posts.filter((post) => {
if (post.tags.includes(self.search.tags)) {
return post;
}
});
if (updated_posts.length) {
updated_term.posts = updated_posts; // now this.terms is changed even though I'm filtering a copy of it
return updated_term;
}
});
} else {
return this.terms; // should return the original, unmanipulated data
}
}
},
filteredTerms() returns categories with only the matching posts inside it. So a search for "angry" returns just "Second Category" with just "Hugo Weaving" listed.
The problem is, running the computed function changes Second Category in this.terms instead of just in the copy of it (terms) in that function. It no longer contains Snake Pliskin. I've narrowed it down to updated_term.posts = updated_posts. That line seems to also change this.terms. The only thing that I can do is reset the entire data object and start over. This is less than ideal, because it would be loading stuff all the time. I need this.terms to load initially, and remain untouched so I can revert to it after someone clears their search criterea.
I've tried using lodash versions of filter and includes (though I didn't really expect that to make a difference). I've tried using a more complicated way with for loops and .push() instead of filters.
What am I missing? Thanks for taking the time to look at this.
Try to clone the object not to reference it, you should do something like :
let terms = [];
Object.assign(terms,this.terms);
let terms = this.terms;
This does not copy an array, it just holds a reference to this.terms. The reason is because JS objects and arrays are reference types. This is a helpful video: https://www.youtube.com/watch?v=9ooYYRLdg_g
Anyways, copy the array using this.terms.slice(). If it's an object, you can use {...this.terms}.
I updated my compute function with this:
let terms = [];
for (let i = 0; i < this.terms.length; i++) {
const term = this.copyObj(this.terms[i]);
terms.push(term);
}
and made a method (this.copyObj()) so I can use it elsewhere. It looks like this:
copyObj: function (src) {
return Object.assign({}, src);
}

JavaScript Deep Extend Objects with Arrays

Building on this angular recursive extend topic.
I have slightly modified the version...
var extendDeep = function(dst) {
angular.forEach(arguments, function(obj) {
if (obj !== dst) {
angular.forEach(obj, function(value, key) {
if (dst[key] && angular.isObject(dst[key])) {
extendDeep(dst[key], value);
} else if(!angular.isFunction(dst[key])) {
dst[key] = value;
}
});
}
});
return dst;
};
but found that it does not account for arrays correctly. For example, if I have a object like:
var obj = { zoo: [ 'moose', 'panda' ] };
and then I call like:
deepExtend(obj, {
zoo: [ 'panda' ]
})
at first glance you would expect it to remove the object, however, it actually ends up like:
{ zoo: [ 'panda', 'panda' ] }
now. The expected output would look like:
{ zoo: [ 'panda' ] }
This is a very simple case of course.
I'm not really looking for a 'unique' or a 'merge' like most solutions talk about. I need to add items that are missing from the left, remove items that are on the left but not on the right, and then iterate recursively and extend those objects in place.
There are many different ways to go about this, looking for feedback on a good approach.

Categories