Related
The sample json object. I tried to remove all "-", "N/A", and "" from this JSON object.
{
name: { first: 'Robert', middle: '', last: 'Smith' },
age: 25,
DOB: '-',
hobbies: [ 'running', 'coding', '-' ],
education: { highschool: 'N/A', college: 'Yale' }
}
I did something like it. but couldn't manage to remove <1 empty item> from the array
{
name: { first: 'Robert', last: 'Smith' },
age: 25,
hobbies: [ 'running', 'coding', <1 empty item> ],
education: { college: 'Yale' }
}
How can I remove the <1 empty item> from the json object.
This is my code
axios.get("https://coderbyte.com/api/challenges/json/json-cleaning")
.then((res) => {
let parseData = res.data;
const removeValue = (obj) => {
Object.keys(obj).forEach(k =>
// console.log(obj[k])
(obj[k] && obj[k] === "-") &&
delete obj[k] ||
(obj[k] && typeof obj[k] === 'object')
&& removeValue(obj[k]) ||
(!obj[k] && obj[k] !== undefined) &&
delete obj[k] ||
(obj[k] && obj[k] === "N/A") &&
delete obj[k]
);
return obj
}
newData = removeValue(parseData)
console.log(newData)
})
Using delete is fine on objects, but for arrays, it will remove the property while keeping the array's .length the same. So, for example, delete-ing the 3rd item of an array of length 3 will result in an array with two array-indexed properties (0 and 1), but keep its length of 3.
Check if the object is an array. If it's an array, .filter instead.
const payload = {
name: { first: 'Robert', middle: '', last: 'Smith' },
age: 25,
DOB: '-',
hobbies: [ 'running', 'coding', '-' ],
education: { highschool: 'N/A', college: 'Yale' }
};
const isGoodValue = val => val && val !== '-' && val !== 'N/A';
const removeBadValues = (obj) => {
if (Array.isArray(obj)) {
return obj
.filter(isGoodValue)
.map(removeBadValues);
}
if (typeof obj === 'object') {
for (const [key, value] of Object.entries(obj)) {
if (!isGoodValue(value)) {
delete obj[key];
} else {
obj[key] = removeBadValues(value);
}
}
}
return obj;
}
const newData = removeBadValues(payload)
console.log(newData)
You can use this
obj.hobbies.splice(obj.hobbies.indexOf('-'), 1);
Where obj is the object you pass in.
Using JSON stringify with replacer argument, Using this method also means the replacement is done to all nested keys within nested objects:
let obj = {
name: { first: "Robert", middle: "", last: "Smith" },
age: 25,
DOB: "-",
hobbies: ["running", "coding", "-"],
education: { highschool: "N/A", college: "Yale" },
};
function replacer(key, value) {
if (value === "N/A" || value === "") {
return undefined;
} else if (Array.isArray(value)) {
return value.filter((el) => el != "-");
}
return value;
}
console.log(JSON.stringify(obj, replacer, 4));
Note: Use JSON.parse() to turn it back into an object.
var test = {
DOB: '-',
test2: 'somestring',
test3: 3,
}
function clean(obj) {
for (var propName in obj) {
if (obj[propName] === '-' || obj[propName] === undefined) {
delete obj[propName];
}
}
return obj
}
console.log(test);
console.log(clean(test));
use Array.prototype.splice()
complete code (recursive) with also empty array or empty object removal
const data_row =
{ name : { first: 'Robert', middle: '', last: 'Smith'}
, age : 25
, DOB : '-'
, hobbies : [ 'running', 'coding', '-']
, education : { highschool: 'N/A', college: 'Yale'}
}
clearner( data_row )
console.log( data_row )
function clearner( obj )
{
const bad = ['-', 'N/A', '']
for (prop in obj)
{
switch (Array.isArray(obj[prop]) ? 'array' : typeof obj[prop])
{
case 'array':
for (let i=obj[prop].length;i--;)
if ( bad.includes( obj[prop][i] ))
obj[prop].splice(i,1);
break;
case 'string':
if (bad.includes( obj[prop] ))
delete obj[prop]
break;
case 'object':
clearner( obj[prop] )
break;
}
if ( obj[prop] && obj[prop].length === 0 ) // empty array or empty object removal
delete obj[prop]
} }
.as-console-wrapper {max-height: 100% !important;top: 0;}
.as-console-row::after {display: none !important;}
I saw these two questions:
Javascript move objects in a nested
array
How to move element in nested
array
But they do not work for me.
So I have a nested dynamic array like this:
const data = [
{
id: 1,
subData: [
{
id: 2,
subData: []
},
{
id: 3,
subData: [
{
id: 4,
subData: []
}
]
}
]
},
{
id: 5,
subData: []
},
.
.
.
]
I have to move the nested elements with their "id". For example, how can I write a function that gives me the following result:
const data = [
{
id: 1,
subData: [
{
id: 2,
subData: []
},
{
id: 3,
subData: [] // object with id 4 was here
}
]
},
{
id: 5,
subData: []
},
{
id: 4, // now its here
subData: []
}
.
.
.
]
What I've tried so far is to write the following function to first find an element with a specific "id" and then move that object:
const findObjById = (obj, key, value) => {
if (obj[key] === value) {
return obj;
}
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
if (obj[k] && typeof obj[k] === 'object') {
const found = findObjById(obj[k], key, value);
if (found) {
return found;
}
}
}
}
Works to find a specific object. But I could not move the found object
Here is an example using a generic traverse function which accepts a visitor callback which is run on each iteration of the traversal and based on the return value of the visitor either returns or continues. (see this answer for more discussion).
We can then create a splice_source traversal which accepts an object to traverse and a predicate to match by and returns the matched element after splicing it from its parent array, and a find_target_array which will return the subData array from an object that matches the passed predicate.
It only remains to push the retrieved source object to the retrieved target array.
This is just an example and will need error checking and streamlining for your particular use cases, but it illustrates some flexible techniques which may be useful moving forward.
const data = [{ id: 1, subData: [{ id: 2, subData: [], }, { id: 3, subData: [{ id: 4, subData: [], },], },], }, { id: 5, subData: [], },];
// generic 'traverse' which accepts a 'visitor' callback
function traverse(o, fn) {
for (const k in o) {
const res = fn.apply(this, [o, k]);
if (res) {
return res;
}
if (o[k] !== null && typeof o[k] == 'object') {
const res = traverse(o[k], fn);
if (res) return res;
}
}
}
// create custom 'visitors' to retrieve source and target arrays
const splice_source = (obj, predicate) =>
traverse(
obj,
// 'visitor' callback
(o, k) => {
let m_index = -1;
if (Array.isArray(o[k])) {
m_index = o[k].findIndex((o) => predicate(o, k));
}
return m_index !== -1 ? o[k].splice(m_index, 1)[0] : false;
});
const find_target_array = (obj, predicate) =>
traverse(
obj,
// 'visitor' callback
(o, k) => (predicate(o, k) ? o.subData : false)
);
// move {id: 4} to subData array of {id: 5}
const source_object = splice_source(data, (obj) => obj?.id === 4);
const target_array = find_target_array(data, (obj) => obj?.id === 5);
target_array.push(source_object);
console.log(JSON.stringify(data, null, 2));
// move {id: 3} to top level 'data' array
data.push(splice_source(data, (obj) => obj?.id === 3));
console.log(JSON.stringify(data, null, 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I need to filter objects recursively in a deeply nested array of objects using javascript, maybe with the help of lodash.
What is the cleanest way to do it, If I don't know how many nested object there will be in my array?
Let's say I have the following structure
[
{
label: "first",
id: 1,
children: []
},
{
label: "second",
id: 2,
children: [
{
label: "third",
id: 3,
children: [
{
label: "fifth",
id: 5,
children: []
},
{
label: "sixth",
id: 6,
children: [
{
label: "seventh",
id: 7,
children: []
}
]
}
]
},
{
label: "fourth",
id: 4,
children: []
}
]
}
];
I want to find the one with id 6, and if it has children return true otherwise false.
Of course If I have a similar data structure but with different number of items it should work too.
Since you only want a true of false answer you can use some() on the recursion, effectively doing a depth-first search, and make it pretty succinct:
let arr = [{label: "first",id: 1,children: []},{label: "second",id: 2,children: [{label: "third",id: 3,children: [{label: "fifth",id: 5,children: []},{label: "sixth",id: 6,children: [{label: "seventh",id: 7,children: []}]}]},{label: "fourth",id: 4,children: []}]}];
function findNested(arr, id) {
let found = arr.find(node => node.id === id)
return found
? found.children.length > 0
: arr.some((c) => findNested(c.children, id))
}
console.log(findNested(arr, 6)) // True: found with children
console.log(findNested(arr, 7)) // False: found no children
console.log(findNested(arr, 97)) // False: not found
Perhaps a recursive solution along the lines of this might work for you? Here, the node with supplied id is recursively searched for through the 'children' of the supplied input data. If a child node with matching id is found, a boolean result is returned based on the existence of data in that nodes children array:
function nodeWithIdHasChildren(children, id) {
for(const child of children) {
// If this child node matches supplied id, then check to see if
// it has data in it's children array and return true/false accordinly
if(child.id === id) {
if(Array.isArray(child.children) && child.children.length > 0) {
return true
}
else {
return false
}
}
else {
const result = nodeWithIdHasChildren(child.children, id);
// If result returned from this recursion branch is not undefined
// then assume it's true or false from a node matching the supplied
// id. Pass the return result up the call stack
if(result !== undefined) {
return result
}
}
}
}
const data = [
{
label: "first",
id: 1,
children: []
},
{
label: "second",
id: 2,
children: [
{
label: "third",
id: 3,
children: [
{
label: "fifth",
id: 5,
children: []
},
{
label: "sixth",
id: 6,
children: [
{
label: "seventh",
id: 7,
children: []
}
]
}
]
},
{
label: "fourth",
id: 4,
children: []
}
]
}
];
console.log('node 6 has children:', nodeWithIdHasChildren( data, 6 ) )
console.log('node 7 has children:', nodeWithIdHasChildren( data, 7 ) )
console.log('node 100 has children:', nodeWithIdHasChildren( data, 7 ), '(because node 100 does not exist)' )
Here is another solution using recursion and doing it via only one Array.find:
const data = [ { label: "first", id: 1, children: [] }, { label: "second", id: 2, children: [ { label: "third", id: 3, children: [ { label: "fifth", id: 5, children: [] }, { label: "sixth", id: 6, children: [ { label: "seventh", id: 7, children: [] } ] } ] }, { label: "fourth", id: 4, children: [] } ] } ];
const search = (data, id) => {
var f, s = (d, id) => d.find(x => x.id == id ? f = x : s(x.children, id))
s(data, id)
return f ? f.children.length > 0 : false
}
console.log(search(data, 6)) // True: found with children
console.log(search(data, 7)) // False: found but has no children
console.log(search(data, 15)) // False: not found at all
The idea is to have a recursive function which when finds the id remembers the object.
Once we have the found (or we know we do not have an entry found) just return the children array length or return false.
If you want to actually return the found object instead of the boolean for children.length:
const data = [ { label: "first", id: 1, children: [] }, { label: "second", id: 2, children: [ { label: "third", id: 3, children: [ { label: "fifth", id: 5, children: [] }, { label: "sixth", id: 6, children: [ { label: "seventh", id: 7, children: [] } ] } ] }, { label: "fourth", id: 4, children: [] } ] } ];
const search = (data, id) => {
var f, s = (d, id) => d.find(x => x.id == id ? f = x : s(x.children, id))
s(data, id)
return f
}
console.log(search(data, 6)) // returns only the object with id:6
console.log(search(data, 7)) // returns only the object with id: 7
console.log(search(data, 71)) // returns undefined since nothing was found
You can use "recursion" like below to check if id has children or not
let arr = [{label: "first",id: 1,children: []},{label: "second",id: 2,children: [{label: "third",id: 3,children: [{label: "fifth",id: 5,children: []},{label: "sixth",id: 6,children: [{label: "seventh",id: 7,children: []}]}]},{label: "fourth",id: 4,children: []}]}];
function hasChildren(arr, id) {
let res = false
for (let d of arr) {
if(d.id == id) return d.children.length > 0
res = res || hasChildren(d.children, id)
if(res) return true
}
return res
}
console.log('id 4 has children? ', hasChildren(arr, 4))
console.log('id 6 has children? ', hasChildren(arr, 6))
You can do it using three simple javascript functions:
// Function to Flatten results
var flattenAll = function(data) {
var result = [];
var flatten = function(arr) {
_.forEach(arr, function(a) {
result.push(a);
flatten(a.children);
});
};
flatten(data);
return result;
};
// Function to search on flattened array
var search = function(flattened, id) {
var found = _.find(flattened, function(d) {
return d.id == id;
});
return found;
};
// Function to check if element is found and have children
var hasChildren = function(element) {
return element && element.children && element.children.length > 0;
}
// Usage, search for id = 6
hasChildren(search(flattenAll(your_data_object), 6))
Plunker
You can use a generator function to iterate the nodes recursively and simplify your logic for checking existence by using Array.prototype.some():
const data = [{label:'first',id:1,children:[]},{label:'second',id:2,children:[{label:'third',id:3,children:[{label:'fifth',id:5,children:[]},{label:'sixth',id:6,children:[{label:'seventh',id:7,children:[]}]}]},{label:'fourth',id:4,children:[]}]}];
function * nodes (array) {
for (const node of array) {
yield node;
yield * nodes(node.children);
}
}
const array = Array.from(nodes(data));
console.log(array.some(node => node.id === 6 && node.children.length > 0));
console.log(array.some(node => node.id === 7 && node.children.length > 0));
The JSON.parse reviver parameter or the JSON.stringify replacer parameter can be used to check all values, and generate flat id lookup object with references to the nodes :
var lookup = {}, json = '[{"label":"first","id":1,"children":[]},{"label":"second","id":2,"children":[{"label":"third","id":3,"children":[{"label":"fifth","id":5,"children":[]},{"label":"sixth","id":6,"children":[{"label":"seventh","id":7,"children":[]}]}]},{"label":"fourth","id":4,"children":[]}]}]'
var result = JSON.parse(json, (key, val) => val.id ? lookup[val.id] = val : val);
console.log( 'id: 2, children count:', lookup[2].children.length )
console.log( 'id: 6, children count:', lookup[6].children.length )
console.log( lookup )
I suggest to use deepdash extension for lodash:
var id6HasChildren = _.filterDeep(obj,
function(value, key, parent) {
if (key == 'children' && parent.id == 6 && value.length) return true;
},
{ leavesOnly: false }
).length>0;
Here is a docs for filterDeep.
And this a full test for your case.
We now use object-scan for data processing needs like this. It's very powerful once you wrap your head around it. This is how you could solve your questions
// const objectScan = require('object-scan');
const hasChildren = (e) => e instanceof Object && Array.isArray(e.children) && e.children.length !== 0;
const find = (id, input) => {
const match = objectScan(['**'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.id === id
})(input);
return hasChildren(match);
};
const data = [{ label: 'first', id: 1, children: [] }, { label: 'second', id: 2, children: [{ label: 'third', id: 3, children: [{ label: 'fifth', id: 5, children: [] }, { label: 'sixth', id: 6, children: [{ label: 'seventh', id: 7, children: [] }] }] }, { label: 'fourth', id: 4, children: [] }] }];
console.log(find(6, data));
// => true
console.log(find(2, data));
// => true
console.log(find(7, data));
// => false
console.log(find(999, data));
// => false
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I have the following code:
You can go to jsbin here..
https://jsbin.com/wupukuhita/1/edit?js,console
var foundObjects = [];
var findObjectByLabel = function(obj, key) {
var foundObject = [];
for(var i in obj) {
if(typeof obj[i] === "object"){
if(typeof obj[i][key] !== "undefined"){
foundObjects.push(obj[i]);
}
findObjectByLabel(obj[i],key);
}
}
return null;
};
I am iterating recursively over an object to find out if a certain property exists.
Then if it does exist, return the parent object.
You can check the jsbin link for a full example.
I do not like the foundObjects which is outside the function.
How can i put it inside the function and just return from the function the objects that contain a certain property.
https://jsbin.com/wupukuhita/1/edit?js,console
You can use javascript closures which is basically a function inside another function and the second function can access the main function objects
see the full code here , it works the same as your except we return the array
var foundObjects = function (obj,key) {
var foundObject = [];
var findObjectByLabel = function(obj,key) {
for (var i in obj) {
if (typeof obj[i] === 'object') {
if (typeof obj[i][key] !== 'undefined') {
foundObject.push(obj[i]);
}
findObjectByLabel(obj[i], key);
}
}
return null;
};
findObjectByLabel(obj,key);
return foundObject ;
}
var mainObj = {
name: 'MainForm', // main form
type: 'Form',
dirty: false,
valid: true,
Errors: [],
formOrInputElements: [
{
name: 'Age', // normal input
type: 'Text',
value: '',
dirty: true,
valid1: true,
test: {
name: 'test',
valid1: false,
},
Errors: [],
},
{
name: 'CNP', // normal input
type: 'Text',
value: '',
dirty: true,
valid: true,
Errors: [],
},
],
};
let foundObject = foundObjects(mainObj, 'valid1');
console.log(foundObject[0]);
console.log(foundObject[1]);
Alternatively, you can use array#reduce and iterate through each of the key values in object. In case of an array, recursively invoke the function
for each object. In case of an object, invoke the function with that object.
var mainObj = { name: "MainForm", type: "Form", dirty: false, valid: true, Errors: [], formOrInputElements: [ { name: "Age", type: "Text", value: "", dirty: true, valid1: true, test: { name: "test", valid1: false }, Errors: [] }, { name: "CNP", type:"Text", value: "", dirty: true, valid: true, Errors: [] } ] }
var findObjectByLabel = function(obj, key) {
return Object.keys(obj).reduce((r, k) => {
if (k === key) {
r.push(Object.assign({}, obj));
} else if (Array.isArray(obj[k])) {
obj[k].forEach(x => r = r.concat(findObjectByLabel(x, key)));
} else if (typeof obj[k] === 'object') {
r = r.concat(findObjectByLabel(obj[k], key));
}
return r;
}, []);
};
console.log(findObjectByLabel(mainObj, "valid1"));
.as-console-wrapper { max-height: 100% !important; top: 0; }
function findObjectByLabel(haystack, needle, buffer = []) {
if (typeof haystack === 'object') {
for (const prop in haystack) {
const result = prop === needle ? [haystack] : findObjectByLabel(haystack[prop], needle);
if (result.length > 0) {
buffer = buffer.concat(result);
}
}
}
return buffer;
}
// Unit test
function test_findObjectByLabel() {
const obj = {
foo: {
foo1: {
item1: 'item1',
item2: 'item2',
item3: 'item3',
},
foo2: {
item1: 'item1',
item2: 'item2',
item3: 'item3',
subFoo: {
item1: 'item1',
item2: 'item2',
needle: 'needle',
}
}
},
bar: {
bar1: {
item1: 'item1',
item2: 'item2',
item3: 'item3',
},
bar2: {
item1: 'item1',
item2: 'item2',
item3: 'item3',
needle: 'needle',
}
},
}
const expected = [
obj.foo.foo2.subFoo, // <-- contain "needle"
obj.bar.bar2, // <-- contain "needle"
];
const actual = findObjectByLabel(obj, 'needle');
if (JSON.stringify(actual) === JSON.stringify(expected)) {
console.log('Pass');
console.log('expected => ', JSON.stringify(expected, null, 4));
console.log('actual => ', JSON.stringify(actual, null, 4));
} else {
console.log('Fail');
console.log('Actual')
console.log(JSON.stringify(actual));
console.log('is not equal to expected');
console.log(JSON.stringify(expected));
}
}
test_findObjectByLabel();
I have objects being sent to me from an API (which cannot change) that follow this type of pattern:
{
aaa: {
name: "Aaa",
desc: "..."
},
bbb: {
name: "Bbb",
desc: "..."
},
ccc: {
name: "Ccc",
desc: "...",
stuff: {
foo: {
bar: "bar"
},
keys: ["foo"]
}
},
keys: ["aaa", "bbb", "ccc"]
}
What I'm trying to do is "flatten" these objects into more usable ones on the client. So the result of flattening the object above would be:
[
{
key: "aaa",
name: "Aaa",
desc: "..."
},
{
key: "bbb",
name: "Bbb",
desc: "..."
},
{
key: "ccc",
name: "Ccc",
stuff: [
{ key: "foo", bar: "bar" }
]
}
]
As you can see there can be 2 levels of data I need to traverse.
Currently I'm looping through and creating the new object (brief ex. below), I'm just wondering if there's a more elegant way to handle this?
var myObjs = [];
$(data.keys).each(function(i, objKey) {
i = $.extend({}, data[objKey], { key: objKey });
myObjs.push(i);
});
This seems to do what you require:
function flatten(obj) {
return $.map(obj.keys, function(key) {
var res = $.extend({}, obj[key], {key: key});
for (var key in res) {
if (typeof res[key] === 'object') {
res[key] = flatten(res[key]);
}
}
return res;
});
}
See http://jsfiddle.net/alnitak/pgq2S/
NB: this will keep on recursing so long as you have nested objects - it doesn't currently stop at two levels.
This should do it:
function toArray(obj) {
if (typeof obj !== 'object' || !("keys" in obj))
return obj;
return obj.keys.map(function(key) {
var sub = obj[key];
if (typeof sub !== 'object')
return sub;
for (var prop in sub)
sub[prop] = toArray(sub[prop]);
sub.key = key;
return sub;
});
}