Flattening json with an key exclusion list - javascript

I need to flatten the json, but want to consider an exclusion_keys_array list which are not to be processed/added to the list
for example
if I have an exclusion_keys_array = ["addresses.metadata", "pageToken"]
//only metadata of addresses will be skipped (second level skip)
if I have an exclusion_keys_array = ["metadata", "pageToken"]
//metadata of parent json will be skipped (top level key skip)
How do I flatten a JSON using an exclusion array?
Code source: Dynamically generate a 2d array from JSON with varying columns
var exlusion_list = ["metadata", "meta", "pageToken"];
var crowds = [{
"name": [{
"firstName": "John",
"middleName": "Joseph",
"lastName": "Briggs",
}],
"addresses": [{
"type": "home",
"poBox": "111",
"city": "City1",
"postalCode": "1ER001",
"country": "USA",
}, {
"type": "work",
"poBox": "222",
"city": "City2",
"region": "Region2",
"postalCode": "1ER002",
}],
"photos": [{
"url": "photo.org/person1",
"default": true,
}, {
"url": "imagur.org/person1",
"default": true,
}],
"metadata": [{
"meta-id": "1234",
}],
}, {
"name": [{
"firstName": "Bill",
"lastName": "Thatcher",
}],
"addresses": [{
"type": "home",
"city": "City3",
"region": "Region3",
"postalCode": "1ER003",
"country": "USA",
}, {
"type": "work",
"poBox": "444",
"region": "Region4",
"postalCode": "1ER004",
}, {
"poBox": "555",
"region": "Region5",
"postalCode": "1ER005",
}],
"metadata": [{
"meta-id": "1234",
}],
}];
function flatten(obj, res = {}, key = '') {
let add = (d, s) => key ? key + d + s : s;
if (Array.isArray(obj)) {
obj.forEach((v, n) => flatten(v, res, add(' #', n + 1)));
} else if (typeof obj === 'object') {
Object.entries(obj).forEach(([k, v]) => flatten(v, res, add(': ', k)));
} else {
res[key] = obj;
}
return res;
}
let flats = crowds.map(obj => flatten(obj));
function combineKeys(objs) {
let keys = objs.reduce((k, obj) => k.concat(Object.keys(obj)), []);
return [...new Set(keys)];
}
let keys = combineKeys(flats);
let table = flats.map(f => keys.map(k => f[k] ?? ''));
table.unshift(keys);
console.log({ table });
// document.write(JSON.stringify(table));
.as-console-wrapper { min-height: 100%!important; top: 0; }
// .as-console-wrapper { min-height: 70%!important; bottom: 0; }

A quick fix would be filter the keys like below. I think there is a more efficient way to do it but I didn't look into the codes too deep.
let keys = combineKeys(flats).filter(
key => !exlusion_list.includes(key.split(":")[0].split(" ")[0])
);

Related

How to combine multiple JSON object that have same key and value

How to combine JSON objects in the same response that has the same key and value with javascript? This is my data for example:
{
"data": [
{
"name": "A",
"description": {
"location": "location1",
"floor": "floor1",
},
},
{
"name": "A",
"description": {
"location": "location2",
"floor": "floor1",
},
},
{
"name": "B",
"description": {
"location": "location3",
"floor": "floor3",
},
},
]
}
And turn it into this:
{
"data": [
{
"name": "A",
"description": {
"location": ["location1","location2"],
"floor": "floor1",
},
},
{
"name": "B",
"description": {
"location": "location3",
"floor": "floor3",
},
},
]
}
Basically I am someone who is new to learning javascript. Any help would be very helpful, thank you.
You can do:
const data = {data: [{name: 'A',description: {location: 'location1',floor: 'floor1',},},{name: 'A',description: {location: 'location2',floor: 'floor1',},},{name: 'B',description: {location: 'location3',floor: 'floor3',},},],}
const result = {
data: data.data.reduce((a, { name, description }) => {
const index = a.findIndex((d) => d.name === name)
if (index >= 0) {
let location = a[index].description.location
location = Array.isArray(location) ? location : [location]
a[index].description.location = [...location, description.location]
} else {
a.push({ name, description })
}
return a
}, []),
}
console.log(result)
const list = {
"data": [
{
"name": "A",
"description": {
"location": "location1",
"floor": "floor1",
},
},
{
"name": "A",
"description": {
"location": "location2",
"floor": "floor1",
},
},
{
"name": "B",
"description": {
"location": "location3",
"floor": "floor3",
},
},
]
};
const consolidatedData = [];
for (const ele of list.data) {
const isExist = consolidatedData.find(x => x.name === ele.name);
if (!isExist) {
consolidatedData.push({
...ele
})
} else {
const objectKey = consolidatedData.findIndex(x => x.name === ele.name);
if (objectKey > -1) {
const description = consolidatedData[objectKey].description;
const newDes = ele.description;
if (newDes.location !== description.location) {
const data = consolidatedData[objectKey].description;
const added = [data.location, ele.description.location];
delete consolidatedData[objectKey].description.location
consolidatedData[objectKey].description["location"] = added
}
if (newDes.floor !== description.floor){
const data = consolidatedData[objectKey].floor;
const added = [data.floor, ele.description.floor];
delete consolidatedData[objectKey].description.floor
consolidatedData[objectKey].description["floor"] = added
}
}
}
}
console.log(JSON.stringify(consolidatedData, null, 2));
Here is a solution that uses an intermediate bucket object. The desired result object is then constructed from the bucket object:
const input = { "data": [ { "name": "A", "description": { "location": "location1", "floor": "floor1", }, }, { "name": "A", "description": { "location": "location2", "floor": "floor1", }, }, { "name": "B", "description": { "location": "location3", "floor": "floor3", }, }, ] };
let buckets = input.data.reduce((acc, obj) => {
if(!acc[obj.name]) {
acc[obj.name] = {
locations: {},
floors: {}
};
}
acc[obj.name].locations[obj.description.location] = true;
acc[obj.name].floors[obj.description.floor] = true;
return acc;
}, {});
console.log('buckets: ', buckets);
let result = {
data: Object.keys(buckets).map(name => {
let locations = Object.keys(buckets[name].locations);
let floors = Object.keys(buckets[name].floors);
return {
name: name,
description: {
location: locations.length == 1 ? locations[0] : locations,
floor: floors.length == 1 ? floors[0] : floors
}
}
})
};
console.log('result:', result);
Notes:
buckets object:
is created using an array .reduce()
array .reduce() docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
locations and floors are collected using objects instead of arrays, this is to avoid duplicate names
result object:
is using Object.keys(buckets) to get the array of names
.map() transforms each name into the desired object
your unusual array or string value for location and floor is constructed with a conditional

Update the array with attributes from different array with same key for both - javascript

I have the following array which is assumed to be large data set.
let response1 = [
{ userID: '2222', dataOne: [ [Object], [Object] ] },
{
userID: '6666',
dataOne: [ [Object] ],
dataTwo: [ [Object], [Object] ]
},
{
userID: '11111',
dataOne: [ [Object], [Object] ],
dataTwo: [ [Object] ]
},
{ userID: '4586', dataTwo: [ [Object] ] }
];
I have another array which i got as a result of database query (which is also a large data set)
let dbResponse = [{
"attributes": {
"dob": "19890147",
"gender": "M",
"mobilePhone": "1239000000",
"name": "Ketan Hol",
},
"doctorID": "ds45ds",
"userID": "11111"
},
{
"attributes": {
"dob": "19890386",
"gender": "M",
"mobilePhone": "1239000000",
"name": "Sachin",
},
"doctorID": "erjjkrel",
"userID": "6666"
},
{
"attributes": {
"dob": "19890219",
"gender": "M",
"mobilePhone": "1239000000",
"name": "Vishwas",
},
"doctorID": "dfgfdg",
"userID": "2222"
},
{
"attributes": {
"dob": "19890219",
"gender": "M",
"mobilePhone": "1239000000",
"name": "Jis",
},
"doctorID": "dfgfdg",
"userID": "98645"
},
{
"attributes": {
"dob": "19890219",
"gender": "M",
"mobilePhone": "1239000000",
"name": "Brad",
},
"doctorID": "dfgfdg",
"userID": "4586"
},
{
"attributes": {
"dob": "19890219",
"gender": "M",
"mobilePhone": "1239000000",
"name": "Brad",
},
"doctorID": "dfgfdg",
"userID": "4586"
}
];
I need to add the attributes such as dob, name from dbResponse to response1 array based on same userID.
All the userID in response1 array should be populated with attributes like dob, name from dbResponse. I am confused on how to perform the below in large data set.
Expected output will be like this:
response1 = [
{ userID: '2222', dataOne: [ [Object], [Object] ], dob: '19890219', name: 'Vishwas' },
{
userID: '6666',
dataOne: [ [Object] ],
dataTwo: [ [Object], [Object] ],
dob: '19890386',
name: 'Sachin'
},
{
userID: '11111',
dataOne: [ [Object], [Object] ],
dataTwo: [ [Object] ],
dob: '19890147',
name: 'Ketan Hol'
},
{ userID: '4586', dataTwo: [ [Object] ], dob: '19890219', name: 'Brad' }
];
What will be the best way to achieve this using es6 functions for a large data sets? I am new to these es6 functions. Any help would be really appreciated.
Approach1
Iterate dbResponse for every userId in response1, extract the object and copy the object in response1.
Approach2 (Optimised operation)
As both are large arrays, you will have to iterate dbResponse a large number of times. To optimize the operation of finding the response1 corresponding userID object in the dbResponse array, you could maintain a mapping to reduce the searching complexity.
const result = dbResponse.reduce((acc, obj) => {
const { userID } = obj
acc[userID] = obj;
return acc;
}, {});
const finalResult = response1.reduce((acc, curr) => {
const { userID } = curr
const dbObj = result[userID] || {}
acc.push({
...curr,
...dbObj
})
return acc;
}, []);
The final result will be in finalResult
So I've been checking this one, #mappie answer Is quite good the indexed way is the fastest way although the finalResult can be done using map rather than reduce, personally thing map is clearer, and seems similar timings.
You can see that using Find is really slow compared to the other 2 solutions.
Here you can find a snippet comparing timeings between methods.
#mappie solution but using Map (but there is no check for incosistency between 2 arrays)
const result = dbResponse.reduce((acc, obj) => {
const { userID } = obj;
acc[userID] = obj;
return acc;
}, {});
const finalResult = response1.map((user) => ({
...user,
...result[user.userID].attributes,
}));
/******************************************************************************
* Mock Data Creation tools
*****************************************************************************/
const getRandomBetween = (start, end) =>
Math.floor(Math.random() * (end - start)) + start;
const getRandomDOB = () =>
getRandomBetween(1950, 2000).toString() +
getRandomBetween(10, 12).toString() +
getRandomBetween(10, 29).toString();
const getRandomGender = () => (getRandomBetween(0, 1) === 0 ? "M" : "F");
const getUserIdGenerator = (state) => () => state.userID++;
const getResponseDocument = (userIdGenerator) => ({
userID: userIdGenerator(),
dataOne: "don't care",
});
const getDbResponseDocument = (userIdGenerator) => ({
attributes: {
dob: getRandomDOB(),
gender: getRandomGender(),
mobilePhone: getRandomBetween(1000000000, 9999999999).toString(),
name: "Ketan Hol",
},
doctorID: "ds45ds",
userID: userIdGenerator(),
});
/******************************************************************************
* Mock Data Creation
*****************************************************************************/
const usersAmount = 50000;
const r1UidGen = getUserIdGenerator({ userID: 10000 });
const response1 = Array.from(Array(usersAmount).keys()).map(() =>
getResponseDocument(r1UidGen)
);
const dbRUidGen = getUserIdGenerator({ userID: 10000 });
const dbResponse = Array.from(Array(usersAmount).keys()).map(() =>
getDbResponseDocument(dbRUidGen)
);
/******************************************************************************
* Different ways to merge the arrays
*****************************************************************************/
function methodIndexed() {
const result = dbResponse.reduce((acc, obj) => {
const { userID } = obj;
acc[userID] = obj;
return acc;
}, {});
const finalResult = response1.reduce((acc, curr) => {
const { userID } = curr;
const dbObj = result[userID] || {};
acc.push({
...curr,
...dbObj.attributes,
});
return acc;
}, []);
return finalResult;
}
function methodIndexedMap() {
const usersById = dbResponse.reduce((acc, obj) => {
const { userID } = obj;
acc[userID] = obj;
return acc;
}, {});
const finalResult = response1.map((user) => ({
...user,
...usersById[user.userID].attributes,
}));
return finalResult;
}
const byUserId = (userId) => (item) => item.userID === userId;
function methodFind() {
return response1.map((user) => ({
...user,
...dbResponse.find(byUserId(user.userID)).attributes,
}));
}
const results = [];
/******************************************************************************
* Test Methods
*****************************************************************************/
function testMethod(name, method) {
const title = `Method: "${name}"`;
console.time(title);
const result = method();
console.timeEnd(title);
// assert data validity
if (
!(
result[42].userID === response1[42].userID &&
result[42].userID === dbResponse[42].userID &&
result[42].dob === dbResponse[42].attributes.dob
)
) {
throw Error(`Method "${name}" does not produce expected output`);
}
}
// difference between these two are too tight to V8 Runtime Optimizations
// so we run them a few time to stabilize
for (let i = 0; i < 10; i++) {
testMethod("Indexed", methodIndexed);
testMethod("Indexed Map", methodIndexedMap);
}
testMethod("Using Find", methodFind);
.as-console-wrapper { max-height: 100% !important; top: 0; }
We can use Array.map to merge the two data sets, also using Array.find to match users from the response1 array with the dbResponse array.
We can then use Object.assign() to copy all properties from the attributes in dbUser to the user object.
let response1 = [ { userID: '2222', dataOne: [ {}, {} ] }, { userID: '6666', dataOne: [ {} ], dataTwo: [ {}, {} ] }, { userID: '11111', dataOne: [ {}, {} ], dataTwo: [ {} ] }, { userID: '4586', dataTwo: [ {} ] } ];
let dbResponse = [{ "attributes": { "dob": "19890147", "gender": "M", "mobilePhone": "1239000000", "name": "Ketan Hol", }, "doctorID": "ds45ds", "userID": "11111" }, { "attributes": { "dob": "19890386", "gender": "M", "mobilePhone": "1239000000", "name": "Sachin", }, "doctorID": "erjjkrel", "userID": "6666" }, { "attributes": { "dob": "19890219", "gender": "M", "mobilePhone": "1239000000", "name": "Vishwas", }, "doctorID": "dfgfdg", "userID": "2222" }, { "attributes": { "dob": "19890219", "gender": "M", "mobilePhone": "1239000000", "name": "Jis", }, "doctorID": "dfgfdg", "userID": "98645" }, { "attributes": { "dob": "19890219", "gender": "M", "mobilePhone": "1239000000", "name": "Brad", }, "doctorID": "dfgfdg", "userID": "4586" }, { "attributes": { "dob": "19890219", "gender": "M", "mobilePhone": "1239000000", "name": "Brad", }, "doctorID": "dfgfdg", "userID": "4586" } ];
function mergeUserData(response, dbResponse) {
return response.map(user => {
// Find the same user in the dbResponse array
let dbUser = dbResponse.find(dbUser => dbUser.userID === user.userID);
if (dbUser) {
// Copy the relevant information
user.name = dbUser.attributes.name;
user.dob = dbUser.attributes.dob;
}
return user;
})
}
console.log("Merged data:", mergeUserData(response1, dbResponse));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Remove "like items" objects in array based off timestamp

I have an array of (could be more than this), that has a uid and a timestamp.
My goal is to cycle through an object and if their uid is equal to each other, only keep the object with the greater timestamp.
[
{
"uid":"u55555",
"timestamp":1536273731,
"id":"8a655addf1293b6d780ff6469c0848dd",
"name":"John Doe",
},
{
"uid":"u55555",
"timestamp":1536273831,
"id":"8v8799817981mcmccm89c81282128cm2",
"name":"John Doe",
},
{
"uid":"u1111",
"timestamp":1536253940,
"id":"c8898202n2nu929n2828998228989h2h2",
"name":"Test Testerson",
},
{
"uid":"u55555",
"timestamp":1536274940,
"id":"fb990b1734e4aaea2e39315952e13123",
"name":"John Doe",
},
{
"uid":"u11111",
"timestamp":1538275741,
"id":"99s9hshs88s8g89898899898897a79s",
"name":"Test Testerson",
},
]
Does anyone know how I would do this?
I've been playing around with the following but can't get it just right.
var result = signatures.filter(function (a) {
//logic here
}, Object.create(null));
You could sort the original array by timestamp and then reduce it to only a set of unique uids using sort and reduce.
var data = [{"uid": "u55555","timestamp": 1536273731,"id": "8a655addf1293b6d780ff6469c0848dd","name": "John Doe",}, { "uid": "u55555", "timestamp": 1536273831, "id": "8v8799817981mcmccm89c81282128cm2", "name": "John Doe", }, { "uid": "u1111", "timestamp": 1536253940, "id": "c8898202n2nu929n2828998228989h2h2", "name": "Test Testerson", }, { "uid": "u55555", "timestamp": 1536274940, "id": "fb990b1734e4aaea2e39315952e13123", "name": "John Doe", }, { "uid": "u11111", "timestamp": 1538275741, "id": "99s9hshs88s8g89898899898897a79s", "name": "Test Testerson", }];
var result = data
.sort((a,b) => b.timestamp - a.timestamp) //Sort by timestamp descending
.reduce((a,i) => a.some(n=>n.uid === i.uid) ? a : [...a, i], []); //If item is already accounted for, ignore it
console.log(result);
You can create an object keyed to the guid and loop through your array adding the item to the object if it either isn't already there or the time is smaller. Then just take the values from that object:
let arr = [{"uid":"u55555","timestamp":1536273731,"id":"8a655addf1293b6d780ff6469c0848dd","name":"John Doe",},{"uid":"u55555","timestamp":1536273831,"id":"8v8799817981mcmccm89c81282128cm2","name":"John Doe",},{"uid":"u1111","timestamp":1536253940,"id":"c8898202n2nu929n2828998228989h2h2","name":"Test Testerson",},{"uid":"u55555","timestamp":1536274940,"id":"fb990b1734e4aaea2e39315952e13123","name":"John Doe",},{"uid":"u11111","timestamp":1538275741,"id":"99s9hshs88s8g89898899898897a79s","name":"Test Testerson",},]
let newArr = Object.values(
arr.reduce((obj, item) => {
if (!obj[item.uid] || obj[item.uid].timestamp < item.timestamp)
obj[item.uid] = item
return obj
}, {}))
console.log(newArr)
You could find the object and check the timestamp or add the actual object to the result set.
var array = [{ uid: "u55555", timestamp: 1536273731, id: "8a655addf1293b6d780ff6469c0848dd", name: "John Doe" }, { uid: "u55555", timestamp: 1536273831, id: "8v8799817981mcmccm89c81282128cm2", name: "John Doe" }, { uid: "u1111", timestamp: 1536253940, id: "c8898202n2nu929n2828998228989h2h2", name: "Test Testerson" }, { uid: "u55555", timestamp: 1536274940, id: "fb990b1734e4aaea2e39315952e13123", name: "John Doe" }, { uid: "u11111", timestamp: 1538275741, id: "99s9hshs88s8g89898899898897a79s", name: "Test Testerson" }],
result = array.reduce((r, o) => {
var index = r.findIndex(({ uid }) => uid === o.uid);
if (index === -1) {
return r.concat(o);
}
if (o.timestamp > r[index].timestamp) {
r[index] = o;
}
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This creates a new Object, which uses keys based on the UID; then populates it with only the last entries. Perhaps not the smallest code possible, but it's a method.
let sorted = {};
let original = [{
"uid": "u55555",
"timestamp": 1536273731,
"id": "8a655addf1293b6d780ff6469c0848dd",
"name": "John Doe",
},
{
"uid": "u55555",
"timestamp": 1536273831,
"id": "8v8799817981mcmccm89c81282128cm2",
"name": "John Doe",
},
{
"uid": "u11111",
"timestamp": 1536253940,
"id": "c8898202n2nu929n2828998228989h2h2",
"name": "Test Testerson",
},
{
"uid": "u55555",
"timestamp": 1536274940,
"id": "fb990b1734e4aaea2e39315952e13123",
"name": "John Doe",
},
{
"uid": "u11111",
"timestamp": 1538275741,
"id": "99s9hshs88s8g89898899898897a79s",
"name": "Test Testerson",
},
];
original.forEach((item) => {
if (sorted[item.uid] == undefined || sorted[item.uid].timestamp < item.timestamp) {
// if key doesn't exist, create it
// if key exists but timestamp is newer, replace it
sorted[item.uid] = {
uid: item.uid,
timestamp: item.timestamp,
id: item.id,
name: item.name
}
}
});
console.log(sorted);

lodash pick object fields from array

I have array of objects:
var results= [
{
"_type": "MyType",
"_id": "57623535a44b8f1417740a13",
"_source": {
"info": {
"year": 2010,
"number": "string",
},
"type": "stolen",
"date": "2016-06-16T00:00:00",
"createdBy": "57469f3c71c8bf2479d225a6"
}
}
];
I need to select specific fields from array. In result, I want to get the following:
[
{
"_id": "57623535a44b8f1417740a13",
"info": {
"year": 2010,
"number": "string"
},
"type": "stolen",
"date": "2016-06-16T00:00:00",
"createdBy": "57469f3c71c8bf2479d225a6"
}
]
As you can see, I want to select _id field and content of _source object. How can I do this with lodash?
I've found .map function, but it doesn't take array of keys:
var res = _.map(results, "_source");
You could do:
var mapped = _.map(results, _.partialRight(_.pick, ['_id', 'info', 'type', 'date', 'createdBy']));
A little explanation:
_.map(): Expects a function which takes each item from the collection so that you can map it to something else.
_.partialRight(): Takes a function which will be called later on with the its arguments appended to the end
_.pick(): Gets the path specified from the object.
In plain Javascript you could iterate with Array#map and assemble a new object for each object without mutilation the original object.
var results = [{ "_type": "MyType", "_id": "57623535a44b8f1417740a13", "_source": { "info": { "year": 2010, "number": "string", }, "type": "stolen", "date": "2016-06-16T00:00:00", "createdBy": "57469f3c71c8bf2479d225a6" } }],
res = results.map(function (a) {
var o = { _id: a._id };
["info", "type", "date", "createdBy"].forEach(function (k) {
o[k] = a._source[k];
});
return o;
});
console.log(res);
I had the same requirement, and the below solution worked best for me.
let users = [
{
"_id": "5ead7783ed74d152f86de7b0",
"first_name": "User First name 1",
"last_name": "User Last name 1",
"email": "user1#example.com",
"phone": 9587788888
},
{
"_id": "5ead7b780d4bc43fd0ef92e7",
"first_name": "User FIRST name 1",
"last_name": "User LAST name 1",
"email": "user2#example.com",
"phone": 9587788888
}
];
users = users.map(user => _.pick(user,['_id','first_name']))
console.log(users)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
var results = [{
_type: "MyType",
_id: "57623535a44b8f1417740a13",
_source: {
info: {
year: 2010,
number: "string",
},
type: "stolen",
date: "2016-06-16T00:00:00",
createdBy: "57469f3c71c8bf2479d225a6"
}
}];
var rootProperty = ['_id']
var innerProperty = '_source'
var myArray = _.map(results, result => _(result)
.pick(rootProperty)
.assign(_.result(result, innerProperty))
.value()
)
console.log(myArray)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
You can map() the result and have each item assign() the _id key-value in an object toegether with the _source object.
results = _.map(results, item => _.assign(
{ _id: item._id },
item._source
));
var results = [{
"_type": "MyType",
"_id": "57623535a44b8f1417740a13",
"_source": {
"info": {
"year": 2010,
"number": "string",
},
"type": "stolen",
"date": "2016-06-16T00:00:00",
"createdBy": "57469f3c71c8bf2479d225a6"
}
}];
results = _.map(results, item => _.assign(
{ _id: item._id },
item._source
));
document.write('<pre>' + JSON.stringify(results, 0, 4) + '</pre>');
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
You may also choose to write this in plain JS:
result = results.map(item => Object.assign(
{ _id: item._id }, item._source
));
var results = [{
"_type": "MyType",
"_id": "57623535a44b8f1417740a13",
"_source": {
"info": {
"year": 2010,
"number": "string",
},
"type": "stolen",
"date": "2016-06-16T00:00:00",
"createdBy": "57469f3c71c8bf2479d225a6"
}
}];
result = results.map(item => Object.assign(
{ _id: item._id }, item._source
));
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
To correctly fulfill the OP's question and for even more complex requirements, the application of a schema and a small lodash mixin is invaluable.
The JavaScript is a little ugly, but it looks swell in CoffeeScript (yes, that was a thing once). The compiled JavaScript is hidden beneath.
_.mixin mapGet: (obj, schema) ->
result = for row in input
row_result = {}
for key, value of schema
row_result[key] = _.get(row, value)
row_result
_.mixin({ mapGet: function(obj, schema) {
var key, result, row, row_result, value;
return result = (function() {
var i, len, results;
results = [];
for (i = 0, len = input.length; i < len; i++) {
row = input[i];
row_result = {};
for (key in schema) {
value = schema[key];
row_result[key] = _.get(row, value);
}
results.push(row_result);
}
return results;
})();
}});
/* The remainer is just the proof/usage example */
var expected, input, schema;
input = [{
"_type": "MyType",
"_id": "57623535a44b8f1417740a13",
"_source": {
"info": {
"year": 2010,
"number": "string"
},
"type": "stolen",
"date": "2016-06-16T00:00:00",
"createdBy": "57469f3c71c8bf2479d225a6"
}}];
expected = [{
"_id": "57623535a44b8f1417740a13",
"info": {
"year": 2010,
"number": "string"
},
"type": "stolen",
"date": "2016-06-16T00:00:00",
"createdBy": "57469f3c71c8bf2479d225a6"
}];
schema = {
"_id": "_id",
"info": "_source.info",
"type": "_source.type",
"date": "_source.date",
"createdBy": "_source.createdBy"
};
console.log('expected result: ' + JSON.stringify(expected, 0, 4));
console.log('actual result: ' + JSON.stringify(_.mapGet(input, schema), 0, 4));
<script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script>
Usage:
schema = {
"_id" : "_id",
"info" : "_source.info",
"type" : "_source.type",
"date" : "_source.date",
"createdBy": "_source.createdBy",
}
_.mapGet(input, schema)
Resultant output:
[{
"_id": "57623535a44b8f1417740a13",
"info": {
"year": 2010,
"number": "string"
},
"type": "stolen",
"date": "2016-06-16T00:00:00",
"createdBy": "57469f3c71c8bf2479d225a6"
}]
Note: Complex schema can be more easily described if the source JSON is first converted to a flat, dotted, representation via:
jq [leaf_paths as $path | {"key":$path | join("."), "value":getpath($path) }] |from_entries'

JavaScript multi-level sorting with multi dimension array

I have a JSON object like below:
[
{
"name": "Robert",
"age":32,
"country": "UK"
},
{
"name": "Prasad",
"age":28,
"country": "India"
},
{
"name": "Benny",
"age":45,
"country": "USA"
},
{
"name": "Robin",
"age":34,
"country": "UK"
},
{
"name": "Bob",
"age":20,
"country": "India"
}
]
I have applied the array sorting for "name" column alone. I want to apply sort for “name” column first and then “age”.
This is how i sort the array by name:
var sort_by = function(field, reverse, primer){
var key = primer ?
function(x) {return primer(x[field])} :
function(x) {return x[field]};
reverse = [-1, 1][+!!reverse];
return function (a, b) {
return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
}
}
Call the sort function:
arrayToSort.sort(
sort_by( “name”, true, function(a){
return a.toUpperCase();
}) );
How can I get the array sorted like below?
[{
"name": "Bob",
"age":20,
"country": "India"
},
{
"name": "Benny",
"age":45,
"country": "USA"
},
{
"name": "Prasad",
"age":28,
"country": "India"
},
{
"name": "Robert",
"age":32,
"country": "UK"
},
{
"name": "Robin",
"age":34,
"country": "UK"
}]
I think what you are looking for is a way to "chain" sort_by(..) calls so as to be able to operate on more than one field.
Below is a slightly modified version of your code. Its pretty much self-explanatory.
arrayToSort = [ ...];
var sort_by = function(field, reverse, primer){
var key = primer ?
function(x) {return primer(x[field]); }:
function(x) {return x[field] };
reverse = [-1, 1][+!!reverse];
return function (a, b) {
a = key(a);
b = key(b);
return a==b ? 0 : reverse * ((a > b) - (b > a));
//^ Return a zero if the two fields are equal!
}
}
var chainSortBy = function(sortByArr) {
return function(a, b) {
for (var i=0; i<sortByArr.length; i++) {
var res = sortByArr[i](a,b);
if (res != 0)
return res; //If the individual sort_by returns a non-zero,
//we found inequality, return the value from the comparator.
}
return 0;
}
}
arrayToSort.sort(
chainSortBy([
sort_by( "name", true, function(a){
return a.toUpperCase();
}),
sort_by("age", true, null)
])
);
console.log(arrayToSort); //Check browser console.
For output: check the JSFiddle
The solution is back to native, just :
function orderByProp(arr,prop){
var order = [], ordered=[];
//create temp ID and Save the real index
for(i=0; i < arr.length;++i){ order.push(arr[i][prop]+"-:-"+i);}
ordered.sort();
for(i=0; i < arr.length;++i){
var val = order.split("-:-");
ordered.push(arr[val[1]]); Get the real array by saved index
}
return ordered;
}
// Apply
var arr = [{
"name": "Bob",
"age":20,
"country": "India"
},
{
"name": "Benny",
"age":45,
"country": "USA"
},
{
"name": "Prasad",
"age":28,
"country": "India"
},
{
"name": "Robert",
"age":32,
"country": "UK"
},
{
"name": "Robin",
"age":34,
"country": "UK"
}];
var sort = orderByProp(arr,"name");
i'm not tested this. but hope it could solve your problems
This is relatively trivial with the Array.sort method by using the || operator, where it will use the second value if the first comparison returns 0, meaning the value was the same:
const data = [
{
"name": "Robert",
"age": 32,
},
{
"name": "David",
"age": 24,
},
{
"name": "Robert",
"age": 28,
},
];
const sortedData = data.sort((a, b) => a.name.localeCompare(b.name) || a.age - b.age);
console.log(sortedData);
Credit for this goes to #NinaScholz for her answer here.

Categories