Object Values sorting with umulate order - javascript

I have been trying to sort the json response object array in specific order like if the object contains Umlaute words/characters
object {
item: 1,
users: [
{name: "A", age: "23"},
{name: "B", age: "24"},
{name: "Ä", age: "27"}
]
}
Expected name sorting is A, Ä and B.
when trying to sort with localecompare().
object.sort(function (a, b) { return a.localeCompare(b); });
Getting the error like object.sort is not a function. Is there anyway to sort the object array.

You need to take the property of the object for sorting.
var object = { item: 1, users: [{ name: "A", age: "23" }, { name: "B", age: "24" }, { name: "Ä", age: "27" }] };
object.users.sort(function (a, b) { return a.name.localeCompare(b.name); });
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You must apply the sort() function to the array, not the outer object. Objects don't have that method and in the general case key order is not guaranteed, anyways. For "ordered" data always use arrays, not objects!
This is an example that uses the o.users array. The output n is also an array.
NOTE: never abuse reserved keywords as variable names. You use object as a variable name, it is strongly recommended to not do so! I've replaced it with o. Albeit object is not a reserved keyword, Object (with capital O) is. And the two are easily confused.
var o = {
"item": 1,
"users": [
{ "name": "A", "age": "23" },
{ "name": "B", "age": "24" },
{ "name": "Ä", "age": "27" }
]
};
var n = o.users.sort((a, b) => a.name.localeCompare(b.name, "de"));
console.log(n);

Related

Is it correct to use .reduce and filter in 1 million json array in node js?

Is it correct to use .reduce and .filter in 1 million json array in node js? I am using JSONStream to parse it. However I need to find the duplicate and unique records and have to do some massage on every records and also have to comparisons between two json array of 1 millions.
What is the best way to approach this? Performance is not a constraint its a background job only. Planning to deploy this job aws ECS using scheduled task.
I am planning to do something like this below
var values = [
{ name: "a", age: 15 },
{ name: "a", age: 17 },
{ name: "a", age: 17 },
{ name: "b", age: 18 },
{ name: "c", age: 18 },
{ name: "d", age: 18 },
{ name: "c", age: 18 },
];
const lookup = values.reduce((a, e) => {
a[e.name] = ++a[e.name] || 0;
return a;
}, {});
console.log("Name count",lookup)
var unique =[];
var dup = values.filter(e => {
if(lookup[e.name]){
return lookup[e.name];
}else {
unique.push(e)
}
});
console.log("Unique:",unique);
console.log("Duplicate", dup)
If you want to know whether this is going to work with a million record array, then you will just have to run it on your system and in your environment to see. That isn't something one can just look at the code and "know". You have to experiment.
I'd have to say that I start out thinking that a million record set of data is probably best managed in a database. If you put all these in a database, then you could structure how you put the data into the database to make it easier to query for all the records with a given name or age or all the records that have a unique name or a non-unique name. This is what databases are built for.
I can see one way that might speed things up a bit. You can build lookup, unique and dup with fewer name lookups. If, while you are building the lookup data structure, you also just keep track of all the items with that particular name, then you can just see which items end up with only one item for a name. So, rather than iterate through all the data and lookup every name, you can just iterate through the lookup data structure and see which names have a cnt of 1 or greater than 1.
I also personally like using a Map object for the lookup since that's what it is built for, but since your keys are strings, your object will probably work just fine too. Anyway, here's a slightly tweaked version:
var values = [
{ name: "a", age: 15 },
{ name: "a", age: 17 },
{ name: "a", age: 17 },
{ name: "b", age: 18 },
{ name: "c", age: 18 },
{ name: "d", age: 18 },
{ name: "c", age: 18 },
];
const lookup = new Map();
const unique = [];
const dup = [];
for (let item of values) {
let array = lookup.get(item.name);
if (!array) {
// if we don't have this name yet, add it to the Map as a one element array
lookup.set(item.name, [item]);
} else {
// otherwise, just push this item into the array
array.push(item);
}
}
// To find unique and dup, we can just iterate the Map and
// look at the length of each array
for (let [name, array] of lookup) {
if (array.length === 1) {
unique.push(array[0]);
} else {
dup.push(...array);
}
}
console.log("Name count", lookup);
console.log("Unique:", unique);
console.log("Duplicate", dup);
The values data structure is not the most efficient way to store a million records that all have the same two properties. It's possible that the interpreter will recognize the internal repetition and optimize it for you, but it could probably be stored more efficiently as:
const values = [
[ "a", 15 ],
[ "a", 17 ],
[ "a", 17 ],
[ "b", 18 ],
[ "c", 18 ],
[ "d", 18 ],
[ "c", 18 ],
];
Or, even just:
const values = [
"a", 15,
"a", 17,
"a", 17,
"b", 18,
"c", 18,
"d", 18,
"c", 18,
];
where the code had some pre-built knowledge for how to get each individual item. You could consider optimizations like this if the memory usage was too high.

How do I transform an array to hold data based on a value in the data?

I have seen some questions that might look similar but none is the solution in my case. I want to regroup and recreate my array the way that it is arranged or grouped based on one of my values(age). I want to have all data of the same "age" in one place. So here is my sample array:
[
{
"age": 15,
"person": {
name: 'John',
hobby: 'ski'
},
},
{
"age": 23,
"person": {
name: 'Suzi',
hobby: 'golf'
},
},
{
"age": 23,
"person": {
name: 'Joe',
hobby: 'books'
}
},{
"age": 25,
"person": {
name: 'Rosi',
hobby: 'books'
}
},{
"age": 15,
"person": {
name: 'Gary',
hobby: 'books'
}
},
{
"age": 23,
"person": {
name: 'Kane',
hobby: 'books'
}
}
]
And I need to have an array that kind of have age as a key and person as value, so each key could have multiple values meaning the value will kind of be an array itself.
I have read this and this questions and many more but they were not exactly the same.
I feel like I need to use reduce to count duplicate ages and then filter it based on that but how do I get the values of those ages?
EIDT:
Sorry for not being clear:
This is what I need:
{
23: [
{ name: 'Suzi', hoby: 'golf' },
{ name: 'Joe', hobby: 'books'}
],
15: [
{ name: 'Gary', hobby: 'books' }
] ,
.
.
.
}
You're actually going to want to reduce, not filter. Filtering an Array means to remove elements and place the kept elements into a new container. Reducing an array means to transform it into a single value in a new container. Mapping an array means to transform every value in place to a new container. Since you want to change how the data is represented that's a Reduction, from one form to another more condensed form.
Assume your Array of values is stored in let people = [...]
let peopleByAge = people.reduce(function (accumulator, value, index, array){
// The first time through accumulator is the passed extra Object after this function
// See the MDN for Array.prototype.reduce() for more information
if (accumulator[value.age] == undefined){
accumulator[value.age] = [];
}
accumulator[value.age].push(value);
return accumulator
}, {})
console.log(peopleByAge) // { 23: [{ age: 23, name: ..., hobby: ...}, ...], 13: [...], ...}
You can find the MDN article for Array#reduce() here
Thanks to #RobertMennell who patiently answered me and I voted as answer. But I just wanted to write my version which MDN had a great example of. It is a longer version assuming the people is the array name:
const groupedByvalue = 'age';
const groupedArray = people;
const groupBy = (peopleArray, value) => {
return peopleArray.reduce((acc, obj) => {
const key = obj[value];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
console.log(groupBy(groupedArray,groupedByvalue));
Update:
More polished using ternary operator:
const groupedByvalue = 'age';
const groupedArray = people;
const groupBy = (peopleArray, value) => {
return peopleArray.reduce((acc, obj) => {
const key = obj[value];
(!acc[key]) ? (acc[key] = []) : (acc[key].push(obj))
return acc;
}, {});
}
console.log(groupBy(groupedArray,groupedByvalue));

Dynamic/computed keys in JMESPath?

From ES2015 with computed properties and Array.reduce/Array.map/Object.assign you can do:
[{name: 'foo', age: 43}, {name: 'bar', age: 55}].map(
o => ({[o.name]: o.age})).reduce((a, b) => Object.assign(a,b), {})
…and get:
{ foo: 43, bar: 55 }
How do I get this from JMESPath?
Attempt:
$echo '[{"name": "foo", "age": 43}, {"name": "bar", "age": 55}]' | jp [].{name:age}
[
{
"name": 43
},
{
"name": 55
}
]
Problem
How to construct a Jmespath query that returns objects with arbitrary key-value pairs
The keys need to be dynamic, based on the output of a jmespath filter expression
Workaround
As of this writing (2019-03-22), dynamic keys are not available in standard Jmespath
However, it is possible to return a list of lists instead of a list of objects, and simply post-process that list of lists outside of jmespath
Example
[*].[#.name,#.age]
Returns
[['foo', 43], ['bar', 55]]
Which can then be post-processed outside of Jmespath, if that is an option for you.
See also
github issue about this exact use-case
To get this result precisely:
{ "foo": 43, "bar": 55 }
You should use this query:
#.{foo: #[0].age, bar: #[1].age}
But as you can see I don't retrieve the keys foo and bar dynamically because I can't do it in JMESPath.

Filtering out JSON by an array

I have a JSON file
{
"data": [
{
"name": "Jake",
"id": "123"
},
{
"name": "Bob",
"id": "234"
}]
}
with all id's unique, and say I have an array of banned ids ["123","423"] and I would like to delete all entries that have an id number in the array (so as an output I'd like the following).
{
"data": [
{
"name": "Bob",
"id": "234"
}]
}
What would be a moderately efficient way (runs in a few seconds on an ordinary computer) to achieve this if there's a few thousand entries in the JSON and array?
You can use the Array.prototype.filter() method in conjunction with .indexOf():
var bannedIds = ["123", "423"];
var input = {
"data": [
{
"name": "Jake",
"id": "123"
},
{
"name": "Bob",
"id": "234"
}]
};
input.data = input.data.filter(function(v) {
return bannedIds.indexOf(v.id) === -1;
});
console.log(input);
If you don't want to overwrite the original array then just assign the result of the .filter() call to a new variable.
If the above turns out to be too slow with your large amount of data, you can try replacing .filter() with a conventional for loop, and/or replacing .indexOf() with a lookup object created from the array of banned ids.
If you can use ES6, you can do this:
const source = {
"data": [
{
"name": "Jake",
"id": "123"
},
{
"name": "Bob",
"id": "234"
}
]
};
const banned = ["123", "423"];
// O(n) startup cost for constant access time later
const bannedSet = new Set(banned);
// O(n)
const result = source.data.filter(x => !bannedSet.has(x.id));
console.log(result);
As mentioned in the comments, there's a startup cost for creating the Set. However, this lets you then call Set.prototype.has, which is constant.
Then, it's just a matter of iterating over every element and filtering out the ones that are in the banned set.
If you can't use ES6, you could replace Set with a plain JS object. If you have to support IE<9, use a polyfill for Array.prototype.filter (thanks #nnnnnn).
UPDATE
#SpencerWieczorek points out that the ES6 spec seems to indicate that Set.prototype.has iterates. I spoke too soon about the lookup being constant (I was carrying over my experience from other languages). Typically, sets will do better than O(n), e.g. constant or O(log n) depending on the underlying implementation. Your mileage may vary, so nnnnnn's answer may be faster in some cases.
Try a few of the solutions here with large amounts of data to confirm.
EDIT
I shied away from using filter or the like because that involves creating a new array. That's actually probably fine for the data sizes we're talking about, but the approach I have below is more efficient.
On my laptop, this whole program runs in about 0.2 seconds. (It uses 10,000 entries and 100 banned IDs.)
var o = {
data: []
};
for (var i = 0; i < 10000; i++) {
o.data.push({
name: i % 2 === 0 ? 'Jake' : 'Bob', // couldn't think of more names :-)
id: ''+i // convert to string
});
}
var banned = {};
for (var i = 0; i < 100; i++) {
banned[''+(i * 3)] = true; // ban 0, 3, 6, 9, 12, ...
}
for (var i = o.data.length - 1; i >= 0; i--) {
if (banned[o.data[i].id]) {
o.data.splice(i, 1);
}
}
console.log(o);
// { data:
// [ { name: 'Bob', id: '1' },
// { name: 'Jake', id: '2' },
// { name: 'Jake', id: '4' },
// { name: 'Bob', id: '5' },
// { name: 'Bob', id: '7' },
// { name: 'Jake', id: '8' },
// { name: 'Jake', id: '10' },
// ...
I am assuming that you have already parsed the JSON data and you have a variable pointing to the array you want to filter. Also, you have an array with the "banned" IDs.
var data = [{
"name": "Jake",
"id": "123"
}, {
"name": "Bob",
"id": "234"
}, {
"name": "Joe",
"id": "345"
}];
var banned = ["123", "345"];
The following function wil probably do the best job that can be done in terms of performance:
// Modifies the data array "in place", removing all elements
// whose IDs are found in the "banned" array
function removeBanned(data, banned) {
// Index the "banned" IDs by writing them as the properties
// of a JS object for really quick read access later on
var bannedObj = {};
banned.forEach(function(b) { bannedObj[b] = true; });
var index = data.length - 1;
while (index >= 0) {
if (bannedObj[data[index].id]) {
data.splice(index, 1);
}
--index;
}
}
This one seems fast enough, but I'd suggest you make a free clean copy instead of modifying the existing array, - it may be faster.
function filterout(o,p,f) {
var i = 0; f = f.join();
while( o[i] ) {
if( f.match( o[i][p] ) ){ o.splice(i,1) }
i++
};
}
var filter = ["123","423"];
var object =
{
"data": [
{
"name": "John",
"id": "723"
},
{
"name": "Jake",
"id": "123"
},
{
"name": "Bob",
"id": "234"
}]
};
filterout( object.data, "id", filter );
console.log(JSON.stringify( object ));

Arrange objects by property with specific order using javascript

Is there a way that I could arrange an array of objects according to a certain property whose order is based on an external array (not just alphabetically, numerically, etc.)?
For example, let's say I have a temporary array:
var tempArray = ["b", "a", "d", "c"];
and an array of objects:
var objectArray = [
{ name: "John",
section: "a" },
{ name: "Joe",
section: "b" },
{ name: "Mike",
section: "c" },
{ name: "Mark",
section: "d"}
];
Can I rearrange objectArray so that the object order would follow the contents of tempArray hence returning objectArray as:
objectArray = [
{ name: "Joe",
section: "b" },
{ name: "John",
section: "a" },
{ name: "Mark",
section: "d" },
{ name: "Mike",
section: "c"}
]; // desired output
Any help is very much appreciated.
something like this:
var sorted = objectArray.sort(function(a, b){
return tempArray.indexOf(a.section)-tempArray.indexOf(b.section)
});
You can do that with Array#sort and Array#indexOf. Array#sort sorts arrays, and you can give it a function to call that accepts two arguments, which are two entries to compare to determine which should be before the other. Array#indexOf tells you the index of an entry in the array. So if you use Array#sort on objectArray, and within your callback use Array#indexOf on tempArray to look up the indexes of each object's section property, then compare the indexes (or probably just subtract the second object's index from the first).

Categories

Resources