I want to loop through 600+ array items in an object and find one particular item based on certain criteria. The array in the object is called "operations" and its items are arrays themselves.
My goal is to get the index of operation's array item which has the deeply nested string "Go".
In the sample below this would be the first element. My problem is that I can check if an array element contains "call" and "draw" but I don't know how to test for the nested dictionary "foobar". I only have basic JavaScript available, no special libraries.
let json = {
"head": {},
"operations": [
[
"call",
"w40",
"draw",
{
"parent": "w39",
"style": [
"PUSH"
],
"index": 0,
"text": "Modify"
}
],
[
"call",
"w83.gc",
"draw",
{
"foobar": [
["beginPath"],
[
"rect",
0,
0,
245,
80
],
["fill"],
[
"fillText",
"Go",
123,
24
],
[
"drawImage",
"rwt-resources/c8af.png",
]
]
}
],
[
"create",
"w39",
"rwt.widgets.Menu",
{
"parent": "w35",
"style": [
"POP_UP"
]
}
],
[
"call",
"w39",
"draw",
{
"parent": "w35",
"style": [
"POP_UP"
]
}
]
]
};
let index = "";
let operationList = json.operations;
for (i = 0; i < operationList.length; i++) {
if (operationList[i].includes('call') && operationList[i].includes('draw')) //missing another check if the dictionary "foobar" exists in this element )
{
index = i;
}
}
document.write(index)
I'll preface by saying that this data structure is going to be tough to manage in general. I would suggest a scheme for where an operation is an object with well defined properties, rather than just an "array of stuff".
That said, you can use recursion to search the array.
If any value in the array is another array, continue with the next level of recursion
If any value is an object, search its values
const isPlainObject = require('is-plain-object');
const containsTerm = (value, term) => {
// if value is an object, search its values
if (isPlainObject(value)) {
value = Object.values(value);
}
// if value is an array, search within it
if (Array.isArray(value)) {
return value.find((element) => {
return containsTerm(element, term);
});
}
// otherwise, value is a primitive, so check if it matches
return value === term;
};
const index = object.operations.findIndex((operation) => {
return containsTerm(operation, 'Go');
});
Related
I'm attempting to search each of the objects listed below from my dataset to see if they contain a value and if they do have that value to add the entire object into a new array.
Example:
Search List of Objects for Data "Continuous", if that data exists (Which it does in Doormerica and Hager) then take the entire object "Doormerica" and "Hager" and put the entire thing into a new array.
{
"Doormerica": {
"Floor Stops": [],
"Overhead Stop": [],
"Pull": [],
"Chain Stop": [],
"Continuous": [
"ALX",
"AL",
"QR"
],
"Kick": [],
"Back to Back Pull": [],
"Concealed": [],
"Butt": [],
"Surface Mount": [],
"Mop": [],
"Armor": [],
"Push": [],
"Wall Stops": []
},
"Schlage": {
"Mortise": [],
"Cylindrical": [
"ALX",
"AL",
"QR"
],
"Deadbolt": [],
"Dummy": [],
"Interconnected": [],
"Cylinders": []
},
"Pemko": {
"Sweeps": [
"345AV"
],
"Permiter Seal": [
"303AS"
],
"Thresholds": [
"170A"
],
"Rain Drip": [
"346C"
],
"Astragal": []
},
"LCN": {
"Surface Mount": [
"4040XP"
],
"Concealed": []
},
"Hager": {
"Butt": [],
"Continuous": []
}
}
Data Transformation
I decided to post this solution because the question asks how to "take the entire object". And including the name (Doormerica, Hager, etc.) in the result, either as a key or a value, seems an important part of the transformation. And we can accomplish this in one pass using Object.entries(), Array.reduce(), Spread Syntax, and the appropriate transformation code.
Examples of both transformations
[{"Doormerica": { "Floor Stops": [], ...}] // name as a key
[{ "Name": "Doormerica", "Floor Stops": [], ...}] // name as a value
Snippet
The snippet shows the complete code for doing both transformations.
const data = {Doormerica:{"Floor Stops":[],"Overhead Stop":[],Pull:[],"Chain Stop":[],Continuous:["ALX","AL","QR"],Kick:[],"Back to Back Pull":[],Concealed:[],Butt:[],"Surface Mount":[],Mop:[],Armor:[],Push:[],"Wall Stops":[]},Schlage:{Mortise:[],Cylindrical:["ALX","AL","QR"],Deadbolt:[],Dummy:[],Interconnected:[],Cylinders:[]},Pemko:{Sweeps:["345AV"],"Permiter Seal":["303AS"],Thresholds:["170A"],"Rain Drip":["346C"],Astragal:[]},LCN:{"Surface Mount":["4040XP"],Concealed:[]},Hager:{Butt:[],Continuous:[]}};
let subset = Object.entries(data).reduce((a,v) =>
v[1].hasOwnProperty("Continuous") ? [...a, {[v[0]]: v[1]} ] : a
// alternative methods adds a name property
// v[1].hasOwnProperty("Continuous") ? [...a, {Name: v[0], ...v[1]} ] : a
, []);
console.log(subset);
So we loop over the values of the object, search for our "term" on each of those values = objects. To search object for property you only need to check if obj[property] exists.
var obj = {Doormerica:{"Floor Stops":[],"Overhead Stop":[],Pull:[],"Chain Stop":[],Continuous:["ALX","AL","QR"],Kick:[],"Back to Back Pull":[],Concealed:[],Butt:[],"Surface Mount":[],Mop:[],Armor:[],Push:[],"Wall Stops":[]},Schlage:{Mortise:[],Cylindrical:["ALX","AL","QR"],Deadbolt:[],Dummy:[],Interconnected:[],Cylinders:[]},Pemko:{Sweeps:["345AV"],"Permiter Seal":["303AS"],Thresholds:["170A"],"Rain Drip":["346C"],Astragal:[]},LCN:{"Surface Mount":["4040XP"],Concealed:[]},Hager:{Butt:[],Continuous:[]}};
function my_search(obj, term) {
return Object.values(obj).reduce(function(agg, value) {
if (value[term]) {
agg.push(value)
}
return agg;
}, [])
}
console.log(my_search(obj, "Continuous"))
.as-console-wrapper {max-height: 100% !important}
I have an object that has unique keys and each key holds an object:
var object = { 'a': {
source: '5279edf0-cd7f-11e3-af07-59475a41e2e9',
target: 'f6b3faa1-ad86-11e3-9409-3dbc47429e9f',
id: [ 'bf504d02-81e2-4a92-9c5c-8101943dc36d' ],
edge_context: [ 'small' ],
statement_id: [ '09b05bc0-20ab-11e9-a5b3-9fb3da66a7cb' ],
weight: 2
},
'b': {
source: '5279edf1-cd7f-11e3-af07-59475a41e2e9',
target: 'f6b3faa1-ad86-11e3-9409-3dbc47429e9f',
id: [ 'de769846-9145-40f8-ab2d-91c0d9b82b27',
'd5723929-71a0-4dfe-bf03-94d43e358145' ],
edge_context: [ 'small' ],
statement_id:
[ '09b05bc0-20ab-11e9-a5b3-9fb3da66a7cb',
'62671510-20ab-11e9-8cbf-ef11fdb08712' ],
weight: 6
}
}
var newArray = [];
for (let item of object) {
newArray(item);
}
console.log(newArray);
I want to map it to another array where the keys will be in a sequence 0, 1, 2 etc as the usual array
I tried to use this function above but it's not working saying "object is not iterable" so how to iterate the object?
Maybe:
const mappedObject = Object.keys(object).map(
k => object[k]
)
As others have pointed out, change the structure. It could be in the following way (you will obtain an array of objects, which you will be able to access using indexes like 0, 1, 2, etc):
var objt = [
{"a": {
"source": "5279edf0-cd7f-11e3-af07-59475a41e2e9",
"target": "f6b3faa1-ad86-11e3-9409-3dbc47429e9f",
"id": [ "bf504d02-81e2-4a92-9c5c-8101943dc36d" ],
"edge_context": [ "small" ],
"statement_id": [ "09b05bc0-20ab-11e9-a5b3-9fb3da66a7cb" ],
"weight": 2
}
},
{"b": {
"source": "5279edf1-cd7f-11e3-af07-59475a41e2e9",
"target": "f6b3faa1-ad86-11e3-9409-3dbc47429e9f",
"id": [ "de769846-9145-40f8-ab2d-91c0d9b82b27",
"d5723929-71a0-4dfe-bf03-94d43e358145" ],
"edge_context": [ "small" ],
"statement_id":
[ "09b05bc0-20ab-11e9-a5b3-9fb3da66a7cb",
"62671510-20ab-11e9-8cbf-ef11fdb08712" ],
"weight": 6
}
}
];
var newArray = objt.map(element => {
const firstProperty = Object.keys(element)[0];
let objectInfo = element[firstProperty];
console.log(objectInfo);
return objectInfo;
});
console.log(newArray);
What happens here is, the only field of each object is not named the same (in one object is "a", the next one is "b", and so on) so we need to figure out to get the only property of each object in the initial array, which contains the information you need to put in another array. For doing this. Object.keys() returns you an array of the properties of an object. Considering the scenario in which we only have one property per object, we get it using Object.keys(element)[0].
Finally, we just use .map() to generate a new array.
I would use Object.values(object) but it's not supported by IE (there is a polyfill for that). Or would use Object.getOwnPropertyNames (which is supported by IE) to convert the keys to an array and map the array to another which containing the values.
var newArray = Object.getOwnPropertyNames(object).map(key => object[key])
I need to get a list of all the key names in the following JSON object:
var myJSON = [
{
"Employees_Name": "Bill Sanders",
"Work_plan_during_my_absence": "Work from home",
"Assigned To-Manager Approval": [
"mymanager#gmail.com"
],
"AbsenceVacation_Summary": [
{
"Computed_Leave_Days": 2,
"From_Date": "2018-08-20",
"To_Date": "2018-08-21",
"Id": "Shccbcc230_a30f_11e8_9afa_25436d674c51"
}
],
"Leave_Type": "Work from Home",
"Reporting_Manager": "My Manager",
"Total_Days": 2,
}
]
When I use the Object.keys method, it retrieves only the top level key names:
var keys_arr = Object.keys(myJSON[0]);
console.log(keys_arr);
The result is an array:
"[ 'Employees_Name', 'Work_plan_during_my_absence', 'Assigned To-Manager
Approval', 'AbsenceVacation_Summary', 'Leave_Type', 'Reporting_Manager',
'Total_Days']"
The key names that are missing are the ones inside of 'AbsenceVacation_Summary'.
I think what I need to do is loop through the array of names returned and see if the value is an object or an array...but I don't know how to do this. Please advise.
You're right you need to walk your object structure recursively to discover nested objects and collects their keys:
function collectKeys(inputObject, outputKeys) {
if (Array.isArray(inputObject)) {
for(let i = 0; i < inputObject.length; i++) {
collectKeys(inputObject[i], outputKeys);
}
} else if (typeof inputObject === 'object') {
Object.keys(inputObject).forEach(function(key) {
outputKeys.push(key);
collectKeys(outputKeys[key], outputKeys);
});
}
}
var collectedKeys = [];
collectKeys(myJSON, collectedKeys);
Working fiddle here
Result will show in console
References
javascript typeof
javascript Array.isArray
javascript Array.forEach
I have a store that looks like this, 3 objects within an array that carry array. How can I add an object in between the first and second object that carry index 1 and 2?
{
object: [
{
"index": 1,
"title": "title",
"caption": "caption",
},
{
"index": 2,
"title": "title",
"caption": "caption",
},
{
"index": 3,
"title": "title",
"caption": "caption",
},
]
}
Would like to have the final output like this after clicking a button that pass in the index value of 1.
{
object: [
{
"index": 1,
"title": "title",
"caption": "caption",
},
{
"index": 2,
"title": "NEW",
"caption": "NEW",
},
{
"index": 3,
"title": "title",
"caption": "caption",
},
{
"index": 4,
"title": "title",
"caption": "caption",
},
]
}
I can use the following codes to change the index value through action, but how to add another new object in between object 1 and object 2, plus changing the index value at the same time?
switch (action.type) {
case ADDCOMPONENT:
return {
...state,
object: state.object.map(component =>
(component.index > action.index ?
{ ...component, index: component.index + 1 } : component)),
};
smth.object.splice(index, 0, item);
And don't keep item index as a string. You can easily get position of item in array, and then add 1 to the value
state.object.splice(component.index, 0, component);
should do the trick
The other answers seem to miss the point that you need to increment the indexes of the other objects in the array. I'd approach it in two steps: first adding the new object with splice, then looping through and incrementing all subsequent indexes.
var index = 1;
state.object.splice(index, 0, new_component); //Insert the new object
//Starting where you inserted, add one to all later components
for (var i = index +1; i < state.object.length; i++) {
state.object[i].index ++;
}
After this state holds the value you want.
However, I'd encourage you to think about if the objects really need to know where they are in the array. In my experience, any code that would need to access the objects would be able to tell where they are in the array.
The shape of your store is created by the combination of all your reducers. The data that you want to update your store with should be created in your application and then sent to your reducer with dispatch(action). The reducer takes the information on the action and updates your state accordingly. If you want to add an object into an array you can use Array.prototype.splice() as follows: myArray.splice( startIndex, 0, itemsToInsert, ... ).
In short, don't add the object in your reducer. Add it to the data you are sending in your action, before you send the action.
If you would like to be able to insert things into an array and not mutate them, you should think about using a function like the one in this snippet:
function nonMutatingArrayInsert( array, index, insert ) {
// return array if index is outside range of array
if ( index < 0 || index > array.length - 1 ) return array;
// ensure insert is array for concat purposes
let _insert = Array.isArray( insert ) ? insert : [ insert ];
// handle 0 index insert
if ( index === 0 ) { return [ ..._insert, ...array ]; }
// handle end of array insert
if ( index === array.length ) { return [ ...array, ..._insert ] }
// handle everyhing else
const before = array.slice( 0, index );
const after = array.slice( index, array.length );
// return new non-mutated array
return [ ...before, ..._insert, ...after ];
}
let myArray = [ "one", "four" ];
let newArray = nonMutatingArrayInsert( myArray, 1, ["two", "three"] );
console.log( "myArray:\n", myArray );
console.log( "newArray:\n", newArray );
All the answers works, however in order to not mutate the array, I have used the following codes instead.
Slice the array before and insert the new object in between.
let newObject = state.portfolio.components.slice(0, action.index);
newObject = newObject.concat(NEWOBJECT);
newObject = newObject.concat(state.portfolio.components.slice(action.index));
for (let i = action.index; i < newComponent.length; i++) {
newComponent[i].index = i + 1;
}
Replace the object with the newObject.
switch (action.type) {
case ADDCOMPONENT:
return {
...state,
object: newObject,
};
EDITED: Used immutability-helper in the end with simpler codes without mutating.
return update(state, {
portfolio: {
object: {
$splice: [
[action.index + 1, 0, action.newObject],
],
},
}
});
I have the following JSON example to represent a tree.
[
{
"label": "node1",
"children": [
{
"label": "Human"
},
{
"label": "Chimpanzee"
}
]
},
{
"label": "node2",
"children": [
{
"label": "node3",
"children": [
{
"label": "Rat"
},
{
"label": "Mouse"
}
]
},
{
"label": "BigRat"
}
]
}
]
What I would like to do now, is the following:
Given a list of labels (e.g. BigRat, Mouse), how can I delete the corresponding nodes in the JSON?
I just can't get my head around this.
Any help is much appreciated!
Thanks
Since your tree is essentially an object array, where each object has a label and an optional child array, what you need to prune are array elements, either in the main tree array or in one of it's branches.
You definitely do not want to use delete to delete array elements, since that leaves you with a sparse array. Instead, you should splice out array elements.
You should, on the other hand, use delete to remove object properties, like children once they're empty. Here's the code I would use:
function prune(array, label) {
for (var i = 0; i < array.length; ++i) {
var obj = array[i];
if (obj.label === label) {
// splice out 1 element starting at position i
array.splice(i, 1);
return true;
}
if (obj.children) {
if (prune(obj.children, label)) {
if (obj.children.length === 0) {
// delete children property when empty
delete obj.children;
// or, to delete this parent altogether
// as a result of it having no more children
// do this instead
array.splice(i, 1);
}
return true;
}
}
}
}
Now assuming your tree was called tree and the label you wanted pruned was node3, you would call prune like so:
var wasItPruned = prune(tree, "node3");