pretty simple question, can't quite fig. it out.
I have 2 js array's that I need to combine into a new array, based on sub_key.
var items = [
Object {
OBJECTID=1,
Name="COMMAND B",
ID="AR0xx",
sub_key="1000"
},
Object {
OBJECTID=2,
Name="95TH PCT",
ID="AR0xx",
sub_key="1001"
},
Object {
OBJECTID=379,
Name="dummy4",
ID="AR0xx",
sub_key="9999"
}
];
var subitems = [
Object {
OBJECTID=787,
ID="AR0xx",
sub_key=1000,
Long_Name = foo
},
Object {
OBJECTID=789,
ID="AR0xx",
sub_key=1001,
Long_Name = "bar"
},
Object {
OBJECTID=1,
ID="AR0xx",
sub_key=1001,
Long_Name="baz"
},
Object {
OBJECTID=788,
ID="AR0xx",
sub_key=1001,
Long_Name="buzzz"
}
];
I'd like to create an array like so, which just combines the above 2, based on sub_key
var data = [
COMMAND B=["foo"],
95TH PCT=["bar","baz","buzz"]
dummy4=[]
];
Here's what I tried but it doesn't work... i think i'm close?? thanks for any help!
data = [];
for (var key in items){
var o = items[key];
//data.push(o.Name);
for (var subkey in subitems){
subo = subitems[subkey];
if (o.sub_key == subo.sub_key){
data[o.Name].push(subo.Long_Name)
}
}
}
Cleaning up your script, here is what you are trying to do. It craetes an array of objects using the Name from items and matching sub_key from sub_items.
var items = [
{ OBJECTID: 1,
Name: 'COMMAND B',
ID: 'AR0xx',
sub_key: '1000'
},
{ OBJECTID: 2,
Name: '95TH PCT',
ID: 'AR0xx',
sub_key: '1001'
},
{ OBJECTID: 379,
Name: 'dummy4',
ID: 'AR0xx',
sub_key: '9999'
}
];
var subitems = [
{ BJECTID: 787,
ID: 'AR0xx',
sub_key: '1000',
Long_Name: 'foo'
},
{ OBJECTID: '789',
ID: 'AR0xx',
sub_key: '1001',
Long_Name: 'bar'
},
{ OBJECTID: '1',
ID: 'AR0xx',
sub_key: 1001,
Long_Name: 'baz'
},
{ OBJECTID: '788',
ID: 'AR0xx',
sub_key: '1001',
Long_Name: 'buzzz'
}
];
var j = subitems.length;
var result = {};
var p;
var sub_key;
var obj;
for (var i=0, iLen = items.length; i<iLen; i++) {
p = items[i].Name;
result[p] = [];
sub_key = items[i].sub_key;
for (var j=0, jLen=subitems.length; j<jLen; j++) {
if (subitems[j].sub_key == sub_key) {
result[p].push(subitems[j].Long_Name);
}
}
}
alert(result['95TH PCT']); // bar, baz, buzz
Edit
Return a single object rather than an array of objects, which I think is what is required.
var newarray = items.slice(0); // make a copy
addloop: for (var i=0; i<subitems.length; i++) {
for (var j=0; j<newarray.length; j++)
if (subitems[i].sub_key == newarray[j].sub_key)
continue addloop;
newarray.push(subitems[i]);
}
should work. Another solution:
Array.prototype.combine = function(a, test) {
if (typeof test == "function") {
for (var i=0; i<a.length; i++)
if (! this.some(test.bind(null, a[i])))
this.push(a[i]);
} else {
for (var i=0; i<a.length; i++)
if (this.indexOf(a[i]) == -1)
this.push(a[i]);
}
return this;
};
var newarray = items.slice(0).combine(subitems, function(a, b) {
return a.sub_key == b.sub_key;
});
I wrote this on the train but didn't get to post it, and it looks like a couple of other people posted good answers since, but it might still be helpful so I'll post it anyways
I had a few different things to note:
var items = [
Object {
OBJECTID=1,
Name="COMMAND B",
ID="AR0xx",
sub_key="1000"
},
...
You don't need the word Object here, you can just write { ... } and JS knows it's an object.
Within an object, you need : instead of =
It's not required, but putting the key in quotes is good practice because some keys won't work otherwise.
So it should look like this:
var items = [
{
"OBJECTID": 1,
"Name": "COMMAND B",
"ID": "AR0xx",
"sub_key": "1000"
},
...
Next up, I'm not completely clear on what you're doing with your data array in the second block, but it looks like you're overriding it with an empty array in the third block.
Also, I think you may be confusing Objects and Arrays somewhat. http://nfriedly.com/techblog/2009/06/advanced-javascript-objects-arrays-and-array-like-objects/ has a good overview of the differences, but here's some key points:
Array is a subclass of Object
Array values always have numeric indexes, not string keys
push() is a method of Array not Object
Next up, your loop. for .. in style loops do work on arrays, but they're not generally recommended because they can also hit keys that were added to the underlying Object. forEach is my favorite but it's not always available in older browsers without a library such as underscore.js.
for(var i=0, len=MyArray.length; i<len; i++) {...} is the other option that you'll see very commonly because it covers all of the array items but does not have the possibility of hitting the underlying object.
But, since Bergi and RobG both have good loops, I'll stop here.
Related
What is the best way to fill in missing properties in an array of objects, such as this example:
[
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
name: 'Richard',
number '07777 666 555'
},
{
name: 'Harry',
website: 'http://www.harry.com'
}
]
I need to add the missing properties with a null value, so that when I pass this array on to be rendered in something such as a HTML table or CSV file, everything lines up correctly. I was thinking of passing over the array twice, once to get all the possible properties, and a second time to add those missing properties with a null value to each object where it doesn't exist. Is there a better way to do this?
EDIT: I won't know what the keys are until I have the data, it's coming from an API and the keys are not always requested explicitly.
My final solution
Thanks all, it seems the two pass approach is indeed the best approach. After I started to write this using the examples provided, I realised that the order of the properties wasn't being maintained. This is how I achieved filling in the missing props, and maintaining the correct order. Any suggestions for potential improvements are welcome.
var fillMissingProps = function(arr) {
// build a list of keys in the correct order
var keys = [];
arr.forEach(function(obj) {
var lastIndex = -1;
Object.keys(obj).forEach(function(key, i) {
if (keys.includes(key)) {
// record the position of the existing key
lastIndex = keys.lastIndexOf(key);
if (lastIndex < i) {
// this key is in the wrong position so move it
keys.splice(i, 0, keys.splice(lastIndex, 1)[0]);
lastIndex = i;
}
} else {
// add the new key in the correct position
// after the previous existing key
lastIndex++;
keys.splice(lastIndex, 0, key);
}
});
});
// build a template object with all props set to null
// and in the correct position
var defaults = {};
keys.forEach(function(key) {
defaults[key] = null;
});
// and update the array by overwriting each element with a
// new object that's built from the template and the original object
arr.forEach(function(obj, i, arr) {
arr[i] = Object.assign({}, defaults, obj);
});
return arr;
};
/** TEST **/
var currentArray = [
{
website: 'http://www.unknown.com'
},
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
title: 'Mr',
name: 'Richard',
gender: 'Male',
number: '04321 666 555'
},
{
id: '003ABCDEFGHIJKL',
name: 'Harry',
website: 'http://www.harry.com',
mobile: '07890 123 456',
city: 'Brentwood',
county: 'Essex'
}
];
var newArray = fillMissingProps(currentArray);
for (var i = 0; i < newArray.length; i++) {
for (var prop in newArray[i]) {
console.log(prop + ": " + newArray[i][prop]);
}
console.log('---------');
}
Given that you don't know apriori which keys are supposed to exist, you have no choice but to iterate over the array twice:
// build a map of unique keys (with null values)
var keys = {}
array.forEach(el => Object.keys(el).forEach(k => keys[k] = null));
// and update the array by overwriting each element with a
// new object that's built from the null map and the original object
array.forEach((el, ix, a) => a[ix] = Object.assign({}, keys, el));
Use Array.prototype.map():
const arr = [
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com',
},
{
name: 'Richard',
number: '07777 666 555',
},
{
name: 'Harry',
website: 'http://www.harry.com',
},
];
const newArr = arr.map(x => (
arr.map(x => Object.keys(x))
.reduce((a, b) =>
(b.forEach(z => a.includes(z) || a.push(z)), a)
)
.forEach(
y => (x[y] = x.hasOwnProperty(y) ? x[y] : null)
), x)
);
console.log(newArr);
Here is a more interesting answer, its a tad fun one but it will build up your objects on the fly as new properties appear:
var currentArray = [
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
name: 'Richard',
number: '07777 666 555'
},
{
name: 'Harry',
website: 'http://www.harry.com'
}
]
var newArray = []
function NewObject() {
}
for(var i = 0; i < currentArray.length; i++){
var nObj = new NewObject();
for(var prop in currentArray[i]){
if(!NewObject.hasOwnProperty(prop))
NewObject.prototype[prop] = null;
nObj[prop]=currentArray[i][prop];
}
newArray.push(nObj);
}
for(var i = 0; i < newArray.length; i++){
for(var prop in newArray[i]){
console.log(prop+ ": "+newArray[i][prop]);
}
console.log('---------');
}
It builds new objects from the ones you provide and adds new properties to the objects if they don't exist already.
This idea was more for curiosities sake tho so any comments would be interesting :)
You can get all keys and set all keys using for..of loop, .map() to iterate all Object.keys(), redefine original array
var arr = [{
name: 'Harry',
website: 'http://www.harry.com'
},{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
}, {
name: 'Richard',
number: '07777 666 555'
}];
for (var obj of arr) {
for (var key of Object.keys(obj)) {
arr = arr.map(o => (o[key] = o[key] || null, o))
}
};
console.log(arr);
Something like this could work:
for (var i = 0; i < arrayLength; i++) {
yourArray[i].name = yourArray[i].name || null;
yourArray[i].number = yourArray[i].number || null;
yourArray[i].website= yourArray[i].website|| null;
}
I have been having some trouble implanting a way to loop through this object that returns this:
{
matches: [
{
region: 'OCE',
platformId: 'OC1',
matchId: 122934310,
champion: 36,
queue: 'TEAM_BUILDER_DRAFT_RANKED_5x5',
season: 'SEASON2016',
timestamp: 1456100362493,
lane: 'BOTTOM',
role: 'DUO_SUPPORT'
},
{
region: 'OCE',
platformId: 'OC1',
matchId: 122510663,
champion: 44,
queue: 'TEAM_BUILDER_DRAFT_RANKED_5x5',
season: 'SEASON2016',
timestamp: 1455751169038,
lane: 'BOTTOM',
role: 'DUO_SUPPORT'
}
],
startIndex: 0,
endIndex: 2,
totalGames: 135
}
I am a php developer so i would use a foreach in this case however I can't seem to figure it out for javascript. Thanks
I need to iterate through each matchId
current Code:
app.get('/summoner/:summonerName', function(req, res) {
lolapi.Summoner.getByName(req.params.summonerName, function(err, obj) {
var options = {
beginIndex: 0,
endIndex: 2
};
lolapi.MatchList.getBySummonerId(obj['savisaar2'].id, options, function(err, matches) {
for(var i = 0; i < obj.matches.length; i++) { console.log(obj.matches[i].matchId); }
});
});
});
Updating this based on the comments below.
Your outer object is called matches. What you are trying to do is iterate over an array (also called matches) within that object, and for each object in the array, print the matchId. You can do that with:
var matches = matches.matches;
// If you get an error here, it means the `matches` object either doesn't exist, or, it doesn't contain an array called `matches`
for(var i = 0; i < matches.length; i++) {
console.log(matches[i].matchId);
}
LoopCount = 0;
while (1==1){
LoopCount = LoopCount + 1;
if (LoopCount == //numberOfTimesToLoop){
break};
//YourCode
};
If your browser supports ES6(most of them are) you can use script like this:
for (var m of obj.matches){
console.log(m.matchId);
}
I have 2 arrays of objects exclude and people, I want to create a new object by checking exclude properties against people properties and only adding objects in people that don't feature in exclude. So far my attempt is a little wild and wondering if someone can help make things a little better or offer a nicer solution?
Fiddle http://jsfiddle.net/kyllle/k02jw2j0/
JS
var exclude = [{
id: 1,
name: 'John'
}];
var peopleArr = [{
id: 1,
name: 'John'
}, {
id: 2,
name: 'James'
}, {
id: 3,
name: 'Simon'
}];
var myObj = [];
for (key in peopleArr) {
for (k in exclude) {
if (JSON.stringify(peopleArr[key]) != JSON.stringify(exclude[k])) {
console.log(peopleArr[key]);
myObj.push(peopleArr[key]);
}
}
}
console.log(myObj);
Under the assumption that exclude can have multiple items, I would use a combination of filter() and forEach() :
var newArray = peopleArr.filter(function(person) {
include = true;
exclude.forEach(function(exl) {
if (JSON.stringify(exl) == JSON.stringify(person)) {
include = false;
return;
}
})
if (include) return person;
})
forked fiddle -> http://jsfiddle.net/6c24rte8/
You repeat some JSON.stringify calls.
You can convert your arrays to JSON once, and then reuse it. Also, you can replace your push by Array.prototype.filter.
var excludeJson = exclude.map(JSON.stringify);
peopleArr = peopleArr.filter(function(x) {
return excludeJson.indexOf(JSON.stringify(x)) === -1;
});
Here is the working snippet:
var exclude = [{
id: 1,
name: 'John'
}];
var peopleArr = [{
id: 1,
name: 'John'
}, {
id: 2,
name: 'James'
}, {
id: 3,
name: 'Simon'
}];
var excludeJson = exclude.map(JSON.stringify);
peopleArr = peopleArr.filter(function(x) {
return excludeJson.indexOf(JSON.stringify(x)) === -1;
});
document.body.innerText = JSON.stringify(peopleArr);
This can be achieved with .filter and .findIndex
var myObj = peopleArr.filter(function(person){
var idx = exclude.findIndex(function(exc) { return person.id == exc.id && person.name == exc.name; });
return idx == -1; // means current person not found in the exclude list
});
I have explicitly compared the actual properties back to the original, there is nothing particularly wrong with your original way of comparing the stringified version (JSON.stringify(e) == JSON.stringify(x) could be used in my example)
I have a multidimensional array but the ID's are unique across parents and children, so I have a problem looping through using a for loop. The problem is that I cannot seem to grab the ID of the children. How do you think I should handle this?
var Options = [
{
id: 0,
children: []
},
{
id: 2,
children: []
},
{
id: 3,
children: [
{
id: 4,
children: []
},
{
id: 5,
children: []
},
{
id: 6,
children: []
}
]
},
{
id: 7,
children: [
{
id: 8,
children: []
},
{
id: 9,
children: []
}
]
}
];
I have kept the code concise for the sake of brevity. What I am trying to do is iterate through the array to compare ID's.
This does not look like a "multidimensional array", but rather like a tree. Looping one level can be done with a simple for-loop:
for (var i=0; i<Options.length; i++) // do something
To loop the tree in-order, you will need a recursive function:
function loop (children, callback) {
for (var i=0; i<children.length; i++) {
callback(children[i]);
loop(children[i].children, callback);
}
}
loop(Options, console.log);
To get all children by their id, so that you can loop through the ids (regardless of the tree structure), use a lookup table:
var nodesById = {};
loop(Options, function(node) {
nodesById[node.id] = node;
});
// access:
nodesById[4];
…and to loop them sorted by id, you now can do
Object.keys(nodesById).sort(function(a,b){return a-b;}).forEach(function(id) {
var node = nodesById[id];
// do something
});
How about recursion?
var findById = function (arr, id) {
var i, l, c;
for (i = 0, l = arr.length; i < l; i++) {
if (arr[i].id === id) {
return arr[i];
}
else {
c = findById(arr[i].children, id);
if (c !== null) {
return c;
}
}
}
return null;
}
findById(Options, 8);
Ah, use recursion :D
var Options = "defined above";//[]
var OptionArray = []; //just as an example (not sure what you want to do after looping)
(function looper(start){
for( var i = 0, len = start.length; i < len; i++ ){
var currentOption = start[i];
if( currentOption.id > 3 ){//could be more complex
OptionArray.push(currentOption);
}
if( currentOption.children.length > 0 ){
looper(currentOption.children);
}
}
})(Options);
Writing a ton of web applications leveraging JSON/AJAX, I find myself returning tons literal javascript objects (JSON). For example, I may be request all the Cats from GetCats.asp. It would return:
[
{ 'id': 0, 'name': 'Persian' },
{ 'id': 1, 'name': 'Calico' },
{ 'id': 2, 'name': 'Tabby' }
]
Now, these are all Cat objects with behaviors. However, if I define a Cat object, function Cat() { }, I know of no EFFICIENT way to coax these literal objects into the behaviors of a user defined object.
I can do this by brute force of iterating through them and assigning functions, but it's not going to be pretty. Is there a nice, one line(or few), way of somehow "casting" this behavior onto these literal objects?
There's no getting around the fact that you will have to iterate through all of your simple objects and change them to a different kind of object. You cannot avoid the loop. That being said you could create a constructor that takes a simple object like this and copies those values into the new instance.
Like this:
function Cat(c) {
this.id = c.id;
this.name = c.name;
}
Cat.prototype.meow = function() {alert('meow');}
Cat.prototype.displayName= function() {alert(this.name);}
var cats = [
{ 'id': 0, 'name': 'Persian' },
{ 'id': 1, 'name': 'Calico' },
{ 'id': 2, 'name': 'Tabby' }
];
for (i=0,len=cats.length; i<len; i++) {
cats[i] = new Cat(cats[i]);
}
cats[0].meow(); // displays "meow"
cats[0].displayName(); // display "Persian"
If you're using the json2 parser (or another one with a compatible interface), you can simply provide a callback to replace the raw objects with custom objects:
var catSource = '[ { "id": 0, "name": "Persian" }, { "id": 1, "name": "Calico" }, { "id": 2, "name": "Tabby" } ]';
function Cat(id, name)
{
this.id = id;
this.name = name;
}
Cat.prototype =
{
toString: function()
{
return this.name;
}
};
function doStuff()
{
var cats = JSON.parse(catSource, function(key, val)
{
// some expression to detect the type of val
if ( val.id !== undefined && val.name !== undefined )
return new Cat(val.id, val.name);
return val;
});
alert(cats);
}
do you use prototype framework? if yes - here is an example
var cats = [
{id: 15, name: 'Tables', count:45 },
{id: 23, name: 'Chairs', count:34 }
];
var catsObjects = [];
cats.each(function(item){
var newObject = new Cat();
Object.extend(newObject, item);
catsObjects.push(newObject);
});
basically Array.each function is the same as "for i < Array.length"
and Object.extend is the same as property-by-property adding properties to newObject
Extending #Benry's answer.
I find that having an extend function to copy properties of one object onto another is essential. There is a semi-tradition for putting it onto Object; Object.extend() exists in many smaller libraries (NB: this is not the same as Object.prototype).
Object.extend = function ( take, give ) {
for (var k in give) {
if (give.hasOwnProperty(k)) {
take[k] = give[k];
}
}
return take;
}
This has the plus that you can use nice readable literal notation when writing your code:
function Cat (c) {
Object.extend( this, ( c || this.defaults ) );
}
Object.extend(Cat.prototype, {
meow : function() {
alert( 'Meow, my name is ' + this.name );
},
defaults : {
name : 'I have no name',
id : null
}
});
You can then create your army of cats quite simply:
var cats = [
{ 'id': 0, 'name': 'Persian' },
{ 'id': 1, 'name': 'Calico' },
{ 'id': 2, 'name': 'Tabby' }
];
for (i=0,len=cats.length; i<len; i++) {
cats[i] = new Cat( cats[i] );
}
cats[0].meow(); // displays "meow, my name is Persian"
Assign each object an appropriate prototype:
var list = [
{ 'id': 0, 'name': 'Persian' },
{ 'id': 1, 'name': 'Calico' },
{ 'id': 2, 'name': 'Tabby' }
];
for (obj in list)
{
obj.prototype = new Cat();
}