Issue with JavaScript recursive function - javascript

I have an object with nested objects. I need to get all the keys and values from all the sub objects into one array.
So I'm trying to do it with a recursive function, but I guess I'm doing something wrong...
The object :
var jsonobj = {
"gender": "male",
"country": "us",
"phone": "06 12 34 56 78",
"enterprise": {
"parameters": {
"company": "foo",
"companyID": "12345678912345",
"address": "adress principale",
}
},
"contacts": [],
"requirements": []
}
Here is the function :
function check(arr){
var val = '';
$.each(arr, function(k, v) {
if (typeof v == "object" && v.length !== 0) {
val = check(v);
}
});
return val;
}
And this is the function using it :
function rec_res(obj_res) {
var foo=[];
$.each(jsonobj, function(k, v) {
if (typeof v == "object" && v.length !== 0) {
g = check(jsonobj); // calling the function
foo.push(g);
} else {
foo.push(v);
}
});
console.log(foo);
};
Expected output:
[foo:{
"gender": "male",
"country": "us",
"phone": "06 12 34 56 78",
"company": "foo",
"companyID": "12345678912345",
"address": "adress principale",
}]
Fiddle

You can create recursive function with Object.keys() and reduce() methods.
var jsonobj = {
"gender": "male",
"country": "us",
"phone": "06 12 34 56 78",
"enterprise": {
"parameters": {
"company": "foo",
"companyID": "12345678912345",
"address": "adress principale",
}
},
"contacts": [],
"requirements": []
}
function rec_res(obj) {
return Object.keys(obj).reduce((r, e) => {
if(typeof obj[e] == 'object') Object.assign(r, rec_res(obj[e]))
else r[e] = obj[e];
return r;
}, {})
}
console.log(rec_res(jsonobj))

var jsonobj = {
"gender": "male",
"country": "us",
"phone": "06 12 34 56 78",
"enterprise": {
"parameters": {
"company": "foo",
"companyID": "12345678912345",
"address": "adress principale",
}
},
"contacts": [],
"requirements": []
}
var result=[];
function rec_res(obj_res) {
var foo=[];
$.each(Object.keys(obj_res), function(k, v) {
if (typeof obj_res[v] == "object") {
var data = rec_res(obj_res[v]);
if(data!=undefined && data.length!=0){
data.map(function(d){
result.push(d);
});
}
} else {
result.push({[v]:obj_res[v]});
foo.push({[v]:obj_res[v]});
}
return foo;
});
//console.log(foo);
};
rec_res(jsonobj);
alert(JSON.stringify(result));

Related

how to sort array of objects by condition javascript

I have array of objects with properties.
I would like to sort by status, that is
15, 17 then 16 at last in javascript
For a array of objects , status having value 16
should be placed at last and rest should sort by ascending as the expected output.
How to do in javascript
var result = arrobj.filter(e=>e.details.status !== 16).sort(a, b) => a.status - b.status;
var arrobj = [
{
"id":1,
"name": 'xyz',
"details": {
"job": 'fulltime',
"status": 15
}
},
{
"id":2,
"name": 'abc',
"details": {
"job": 'partime',
"status": 16
}
},
{
"id":3,
"name": 'zen',
"details": {
"job": 'fulltime',
"status": 17
}
},
{
"id":5,
"name": 'abu',
"details": {
"job": 'fulltime',
"status": 16
}
},
{
"id":7,
"name": 'john',
"details": {
"job": 'parttime',
"status": 15
}
},
{
"id":10,
"name": 'jocob',
"details": {
"job": 'fulltime',
"status": 17
}
}
]
Expected Output
[
{
"id":1,
"name": 'xyz',
"details": {
"job": 'fulltime',
"status": 15
}
},
{
"id":7,
"name": 'john',
"details": {
"job": 'parttime',
"status": 15
}
},
{
"id":3,
"name": 'zen',
"details": {
"job": 'fulltime',
"status": 17
}
},
{
"id":10,
"name": 'jocob',
"details": {
"job": 'fulltime',
"status": 17
}
},
{
"id":2,
"name": 'abc',
"details": {
"job": 'partime',
"status": 16
}
},
{
"id":5,
"name": 'abu',
"details": {
"job": 'fulltime',
"status": 16
}
}
]
const results = [...arrobj.filter(ob => ob.details.status !== 16).sort((a,b) => a.details.status - b.details.status), ...arrobj.filter(ob => ob.details.status === 16)]
You mean this?
We can customize sort rules using the compareFn in Array.prototype.sort(compareFn).
Example:
var result = arrobj
.sort((obja, objb) => {
let a = obja.details.status
let b = objb.details.status
if (a == 16) a = Number.MAX_SAFE_INTEGER
if (b == 16) b = Number.MAX_SAFE_INTEGER
return a - b
})
for es5
arrobj.sort((a, b) => {
if (a.details.status === 16) {
return 1;
} else if (b.details.status === 16) {
return -1
} else {
return a.details.status - b.details.status
}
})
From EcmaScript 2019 Array.sort is stable. This mean that you can split that complex sorting into 2 - first by status, then placing all items with status 16 in the back. Not efficient solution
arrobj.sort((first,second) => first.details.status - second.details.status)
.sort((first, second) => (first.details.status === 16) - (second.details.status === 16));
The below will also work for your use case. You were really close though
arrobj.filter(e=>e.details.status !== 16).sort((a, b) => {return a.details.status - b.details.status}).concat(arrobj.filter(e=>e.details.status == 16));
Your sort function is not correct. Try this one. I also add the ability to change the sort order.
// Sort by status
function sortByStatus(array, asc = true) {
let newArray = [...array];
// filter and sort
if (asc) {
newArray = newArray.filter(e => e.details.status !== 16).sort((a, b) => a.details.status > b.details.status && 1 || -1);
} else {
newArray = newArray.filter(e => e.details.status !== 16).sort((a, b) => a.details.status < b.details.status && 1 || -1);
}
return newArray;
}
// merge result
const result = [...sortByStatus(arrobj), arrobj.filter(e => e.details.status === 16)];
var arrobj = [{
"id": 1,
"name": 'xyz',
"details": {
"job": 'fulltime',
"status": 15
}
},
{
"id": 2,
"name": 'abc',
"details": {
"job": 'partime',
"status": 16
}
},
{
"id": 3,
"name": 'zen',
"details": {
"job": 'fulltime',
"status": 17
}
},
{
"id": 5,
"name": 'abu',
"details": {
"job": 'fulltime',
"status": 16
}
},
{
"id": 7,
"name": 'john',
"details": {
"job": 'parttime',
"status": 15
}
},
{
"id": 10,
"name": 'jocob',
"details": {
"job": 'fulltime',
"status": 17
}
}
];
// Sort by status
function sortByStatus(array, asc = true) {
let newArray = [...array];
// filter and sort
if (asc) {
newArray = newArray.filter(e => e.details.status !== 16).sort((a, b) => a.details.status > b.details.status && 1 || -1);
} else {
newArray = newArray.filter(e => e.details.status !== 16).sort((a, b) => a.details.status < b.details.status && 1 || -1);
}
return newArray;
}
// merge result
const result = [...sortByStatus(arrobj), arrobj.filter(e => e.details.status === 16)];
console.log(result);
You had to add .status in the sort function sort()
To place the object with status 16 at the end, I made an seperate array with status 16 and added on the end of the array with everything else.
var result = arrobj.filter(e=>e.details.status !== 16).sort( (a, b) => a.details.status - b.details.status);
result = result.concat(arrobj.filter(e=>e.details.status == 16));
console.log(result)

Flattening json with an key exclusion list

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])
);

Find the latter date in an object

I need iterate specific objects in an object, and find the object with latter Date.
Here is example of my object:
var o = {
"data": [
{
"id": 2,
"category": "test1",
"parents": [
{
"id": 31,
"children": [
{
"firstName": "Steve",
"lastName": "Martin",
"created": "2018-04-06T22:00:00.000Z"
},
{
"firstName": "Steve2",
"lastName": "Martin2",
"created": "2016-02-10T23:00:00.000Z"
}
]
},
{
"id": 31,
"children": [
{
"firstName": "Julia",
"lastName": "Robbery",
"created": "2015-01-06T23:00:00.000Z"
},
{
"firstName": "Nikol",
"lastName": "Surachenko",
"created": "2017-04-06T22:00:00.000Z"
},
{
"firstName": "Nikol",
"lastName": "Surachenko",
"created": "2011-06-05T22:00:00.000Z"
}
]
}
]
}
]
}
I tried this:
var latter = null;
for (var i = 0; i < o.data[0].parents.length; i++) {
for (var j = 0; j < o.data[0].parents[i].children.length; j++) {
if (latter == null || moment(latter) < moment(o.data[0].parents[i].children[j].created))
latter=o.data[0].parents[i].children[j].created;
}
}
Can you tell me if exist some prettier way? For example with lambda, etc.?
Thanks in advice.
"pretty" is subjective, but in my opinion with lodash you could write it in a bit cleaner way:
mostRecent = _.max(
_.flatMap(
_.get(o, 'data.0.parents'),
'children'),
'created')
If lodash is not an option, you can roll out your own ad-hoc microframework:
let get = p => o => o[p];
let flatMap = (a, f) => [].concat(...a.map(f));
let max = (a, f) => a.map(x => [x, f(x)]).reduce((m, p) => m[1] > p[1] ? m : p)[0];
mostRecent = max(
flatMap(o.data[0].parents, get('children')),
get('created')
)
Using for-loops and compare dates.
This approach downstreams into the whole object to get the right object.
var o = { "data": [{ "id": 2, "category": "test1", "parents": [{ "id": 31, "children": [{ "firstName": "Steve", "lastName": "Martin", "created": "2018-04-06T22:00:00.000Z" }, { "firstName": "Steve2", "lastName": "Martin2", "created": "2016-02-10T23:00:00.000Z" } ] }, { "id": 31, "children": [{ "firstName": "Julia", "lastName": "Robbery", "created": "2015-01-06T23:00:00.000Z" }, { "firstName": "Nikol", "lastName": "Surachenko", "created": "2017-04-06T22:00:00.000Z" }, { "firstName": "Nikol", "lastName": "Surachenko", "created": "2011-06-05T22:00:00.000Z" } ] } ] }] }
var result = {};
for (var obj of o.data) {
for (var p of obj.parents) {
for (var c of p.children) {
result = !result.created || Date.parse(c.created) > Date.parse(result.created) ? c : result;
}
}
}
console.log(result);
You might do it a bit more functional:
const result = o.data[0].parents.reduce((res, {children}) => res.concat(children), [])
.reduce((old, curr) => Date(old.created) > Date(curr.created) ? old : curr);

Resolve References in JSON : Javascript / JSON

I have the following sample JSON coming from a server. Duplicate objects are being internally referred to by an id (see the JSON below).
[
{ "id": 1,
"agent": {
"id": 1,
"firstName": "gghg",
"lastName": "gh",
"phone": "4543534",
"admin": true
},
"user":"agent#gmail.com"
},
{ "id": 2,
"agent": 1, // here I want the full object and not the Id
"user":"agent1#gmail.com"
}
]
Question:
How do I resolve the objects referred to in this fashion given a random JSON object?
(For instance, for the sample JSON above, I will have the below output:)
[
{ "id": 1,
"agent": {
"id": 1,
"firstName": "gghg",
"lastName": "gh",
"phone": "4543534",
"admin": true
},
"user":"agent#gmail.com"
},
{ "id": 2,
"agent": {
"id": 1,
"firstName": "gghg",
"lastName": "gh",
"phone": "4543534",
"admin": true
},
"user":"agent1#gmail.com"
}
]
Basically a single loop proposal, which collects unresolved links and if found the it replaces the open parts with the object.
var data = [{ "id": 1, "agent": { "id": 1, "firstName": "gghg", "lastName": "gh", "phone": "4543534", "admin": true }, "user": "agent#gmail.com" }, { "id": 2, "agent": 1, "user": "agent1#gmail.com" }];
data.forEach(function (a) {
if (typeof a.agent === 'object') {
this[a.agent.id] = this[a.agent.id] || {};
this[a.agent.id].data = a.agent;
this[a.agent.id].update && this[a.agent.id].update.forEach(function (b) {
b.agent = a.agent;
});
return;
}
this[a.agent] = this[a.agent] || {};
if (this[a.agent].data) {
a.agent = this[a.agent].data;
return;
}
this[a.agent].update = this[a.agent].update || [];
this[a.agent].update.push(a);
}, Object.create(null));
console.log(data);
Edit, a more generic version for unknown property references.
var data = [
{ id: 1, agent: { id: 1, firstName: "gghg", lastName: "gh", phone: "4543534", admin: true }, user: "agent#gmail.com", abc: 2 },
{ id: 2, agent: 1, user: "agent1#gmail.com", abc: { id: 2, text: 'blabla' } },
{ id: 3, agent: { id: 1, firstName: "gghg", lastName: "gh", phone: "4543534", admin: true }, user: "agent#gmail.com" },
];
data.forEach(function (a) {
Object.keys(a).forEach(function (k) {
if (typeof a[k] === 'object' && 'id' in a[k]) {
this[a[k].id] = this[a[k].id] || {};
this[a[k].id].data = a[k];
this[a[k].id].update && this[a[k].id].update.forEach(function (b) {
b[k] = a[k];
});
return;
}
this[a[k]] = this[a[k]] || {};
if (this[a[k]].data) {
a[k] = this[a[k]].data;
return;
}
this[a[k]].update = this[a[k]].update || [];
this[a[k]].update.push(a);
}, this);
}, Object.create(null));
console.log(data);
try this
var data = [
{ "id": 1,
"agent": {
"id": 1,
"firstName": "gghg",
"lastName": "gh",
"phone": "4543534",
"admin": true
},
"user":"agent#gmail.com"
},
{ "id": 2,
"agent": 1, // here I want the full object and not the Id
"user":"agent1#gmail.com"
}
];
var map = {};
//create a map of items by their id
data.forEach( function(obj){ map[ obj.id ] = obj.agent; } );
//iterate the data array and replace agents by their value if their value is a number.
data = data.map( function(obj){
if ( !isNaN( obj.agent ) )
{
obj.agent = JSON.parse( JSON.stringify( map[ obj.agent ] ) );
}
return obj;
});
console.log( data );
I think the only way is to run through the array two times:
UPD:
var arr = [ ... ]; // your list of data
var collectionFields = ['agent', 'someOtherField'];
var collections = {};
// collect all information about agents
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
for (var k = 0; k < collectionFields.length; k++) {
var field = collectionFields[k];
if (typeof collections[field] === 'undefined') {
collections[field] = {};
}
if (typeof item[field] === 'object') {
collections[field][item[field].id] = item[field];
}
}
}
for (var j = 0; j < arr.length; j++) {
for (var k = 0; k < collectionFields.length; k++) {
var field = collectionFields[k];
if (typeof arr[j][field] === 'number') {
arr[j][field] = collections[field][arr[j][field]];
}
}
console.log(arr);
var a = [ { "id": 1, "agent": {"id": 1, "firstName": "gghg", "lastName": "gh", "phone": "4543534",
"admin": true}, "user":"agent#gmail.com"},
{ "id": 2, "agent": 1, "user":"agent1#gmail.com"}];
//on = $.parseJSON(a);
console.log(a);
//nsole.log(bson);
var b=[];
var mapID = [];
for(var key in a) {
console.log(a[key].agent);
b[key] = a[key];
if($.isNumeric(a[key].agent)){
var id = a[key].agent;
b[key].agent = a[mapID[id]].agent;
}
mapID[a[key].id] = key;
}
console.log(b);
check working demo
You can simply define a getter for items which has only the agent ID.
var data = [{ "id": 1, "agent": { "id": 1, "firstName": "gghg", "lastName": "gh", "phone": "4543534", "admin": true }, "user": "agent#gmail.com" }, { "id": 2, "agent": 1, "user": "agent1#gmail.com" }];
console.clear();
data.forEach((hash => d => {
var agent = d.agent;
if (typeof agent === 'number')
Object.defineProperty(d, 'agent', {
get: () => hash[agent]
});
else
hash[agent.id] = agent;
})(Object.create(null)));
console.log(data);

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