Hi everyone I have following code
I want to show my DefaultValue nested objects like this
["DefaultValue", "DefaultValue", "DefaultValue","DefaultValue","DefaultValue","DefaultValue"]
I have following data from backend:
const data = [
{
id: 243,
Name: "test",
type: "checkbox",
DefaultValue: {
DefaultValue: {
DefaultValue: {
DefaultValue: {
DefaultValue: {
DefaultValue: ["a"]
}
}
}
}
}
}
];
So I am trying to do following, but it's not works, its says like Cannot convert undefined or null to object
const innerObject = o => {
return Object.keys(o).reduce(function (r, k) {
return typeof o[k] === 'object' ? innerObject(o[k]) : ((r[k] = o[k]), r);
}, {});
};
Please help me to resolve this problem.
you can try this:
const data = [
{
id: 243,
Name: "test",
type: "checkbox",
DefaultValue: {
DefaultValue: {
DefaultValue: {
DefaultValue: {
DefaultValue: {
DefaultValue: ["a"]
}
}
}
}
}
}
];
const makeArr = (obj, arr = []) =>{
if(typeof obj === 'object' && obj !== null){
arr.push('DefaultValue');
return makeArr(obj.DefaultValue, arr)
}else{
return arr;
}
}
console.log(makeArr(data[0].DefaultValue))
I got a schema object that looks like this:
const schema = {
social: {
facebook: 'someValue',
twitter: {
department: {
departmentImage: {
editable: 'someValue'
}
}
}
}
};
The editable property indicates a value that I want to edit, and may appear in several nested locations in the object.
My approach to edit it is to recursively create a new object who is an exact copy of the original, and populate a new value where I encounter editable.
Like this:
const formatSchema = (schema, data, formattedSchema = {}) => {
for (const schemaKey in schema) {
const firstKey = Object.keys(schema[schemaKey])[0];
if (schema[schemaKey] instanceof Object) {
formattedSchema[schemaKey] = schema[schemaKey];
formatschema(schema[schemaKey], data, formattedSchema[schemaKey]);
}
if (schema[schemaKey] instanceof Object && firstKey === 'editable') {
*replacing data logic*
formattedSchema[schemaKey] = ...*replacingData*;
formatschema(schema[schemaKey], data, formattedSchema[schemaKey]);
} else {
formattedSchema[schemaKey] = schema[schemaKey];
}
}
return formattedSchema;
};
But I feel this solution may be inefficient as I create every single bit of the object from scratch and this would happen thousands of times a day.
Is there a way to do it better?
Here's a recursive immutable update that works for any native input type. Don't worry about performance here as it's plenty fast, even if your object has thousands of fields. Let me know how this suits you and I can make a change if it's needed -
function update(t, func) {
switch (t?.constructor) {
case Object:
return Object.fromEntries(
Object.entries(t).map(([k,v]) =>
[k, func([k, update(v, func)])]
)
)
case Array:
return t.map((v, k) => func([k, update(v, func)]))
default:
return func([null, t])
}
}
const schema = {
social: {
facebook: 'someValue',
twitter: {
department: {
departmentImage: {
editable: 'someValue'
}
},
someArr: [{ editable: 1 }, { editable: 2 }, { hello: "world" }]
},
}
}
console.log(update(schema, ([k,v]) =>
k == "editable" ? "✅" : v
))
.as-console-wrapper {min-height: 100% !important; top: 0}
{
"social": {
"facebook": "someValue",
"twitter": {
"department": {
"departmentImage": {
"editable": "✅"
}
},
"someArr": [
{
"editable": "✅"
},
{
"editable": "✅"
},
{
"hello": "world"
}
]
}
}
}
Given an object or array, I want to be able to determine if the path exists or not.
Given - Example 1
const spath = "data/message";
const body = {
data: {
school: 'yaba',
age: 'tolu',
message: 'true'
},
time: 'UTC',
class: 'Finals'
}
it should return true because message can be found in body.data.message else return false.
Given - Example 2
const spath = "data/message/details/lastGreeting";
const body = {
data: {
school: 'yaba',
age: 'tolu',
message: {
content: 'now',
details: {
lastGreeting: true
}
}
},
time: 'UTC',
class: 'Finals'
}
it should return true because lastGreeting can be found in body.data.message.details.lastGreeting else return false.
The other condition is when the body consists of an array
Given - Example 3
const spath = "data/area/NY";
const body = {
data: {
school: 'yaba',
age: 'tolu',
names : ['darious'],
area: [{
NY: true,
BG: true
]]
message: {
content: 'now',
details: {
lastGreeting: true
}
}
},
time: 'UTC',
class: 'Finals'
}
it should return true because NY can be found in body.data.area[0].NY else return false.
This is the solution I came up with
const findPathInObject = (data, path, n) => {
console.log('entered')
console.log(data, path)
if(!data){
return false
}
let spath = path.split('/');
for(let i = 0; i<n; i++){
let lastIndex = spath.length - 1;
if(spath[i] in data && spath[i] === spath[lastIndex]){
return true
}
const currentIndex = spath[i];
// spath.splice(currentIndex, 1);
return findPathInObject(data[spath[currentIndex]], spath[i+1], spath.length)
}
return false
}
console.log(findPathInObject(body, spath, 3))
You could take some checks in advance and check if path is an empry string, then exit with true.
By having an array, you could exit early by checking the elements of the array with the actual path by omitting the indices.
For the final check of a key, you could check the existence of it and return the result of the recursove call with the rest path or return false, if the key is not in the object.
const
findPathInObject = (data, path) => {
if (!path) return true;
if (!data || typeof data !== 'object') return false;
if (Array.isArray(data)) return data.some(d => findPathInObject(d, path));
const
spath = path.split('/'),
key = spath.shift();
return key in data
? findPathInObject(data[key], spath.join('/'))
: false;
};
console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', message: 'true' }, time: 'UTC', class: 'Finals' }, "data/message", 3)); // true
console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' }, "data/message/details/lastGreeting", 3)); // true
console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', names: ['darious'], area: [{ NY: true, BG: true }], message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' }, "data/area/NY", 3)); // true
find
For this answer, I'm going to provide a tree with varying degrees of nesting of objects and arrays -
const tree =
{ data:
{ school: "yaba", age: "tolu", message: "foo" }
, classes:
[ { name: "math" }, { name: "science" } ]
, deep:
[ { example:
[ { nested: "hello" }
, { nested: "world" }
]
}
]
}
Generators are a fantastic fit for this type of problem. Starting with a generic find which yields all possible results for a particular path -
function find (data, path)
{ function* loop (t, [k, ...more])
{ if (t == null) return
if (k == null) yield t
else switch (t?.constructor)
{ case Object:
yield *loop(t[k], more)
break
case Array:
for (const v of t)
yield *loop(v, [k, ...more])
break
}
}
return loop(data, path.split("/"))
}
Array.from(find(tree, "classes/name"))
Array.from(find(tree, "deep/example/nested"))
Array.from(find(tree, "x/y/z"))
[ "math", "science" ]
[ "hello", "world" ]
[]
find1
If you want a function that returns one (the first) result, we can easily write find1. This is particularly efficient because generators are pauseable/cancellable. After the first result is found, the generator will stop searching for additional results -
function find1 (data, path)
{ for (const result of find(data, path))
return result
}
find1(tree, "data/school")
find1(tree, "classes")
find1(tree, "classes/name")
find1(tree, "deep/example/nested")
find1(tree, "x/y/z")
"yaba"
[ { name: "math" }, { name: "science" } ]
"math"
"hello"
undefined
exists
If you care to check whether a particular path exists, we can write exists as a simple specialisation of find1 -
const exists = (data, path) =>
find1(data, path) !== undefined
exists(tree, "data/school")
exists(tree, "classes")
exists(tree, "deep/example/nested")
exists(tree, "x/y/z")
true
true
true
false
demo
Expand the snippet below to verify the results in your own browser -
function find (data, path)
{ function* loop (t, [k, ...more])
{ if (t == null) return
if (k == null) yield t
else switch (t?.constructor)
{ case Object:
yield *loop(t[k], more)
break
case Array:
for (const v of t)
yield *loop(v, [k, ...more])
break
}
}
return loop(data, path.split("/"))
}
function find1 (data, path)
{ for (const result of find(data, path))
return result
}
const tree =
{ data:
{ school: "yaba", age: "tolu", message: "foo" }
, classes:
[ { name: "math" }, { name: "science" } ]
, deep:
[ { example:
[ { nested: "hello" }
, { nested: "world" }
]
}
]
}
console.log("find1")
console.log(find1(tree, "data/school"))
console.log(find1(tree, "classes"))
console.log(find1(tree, "classes/name"))
console.log(find1(tree, "deep/example/nested"))
console.log(find1(tree, "x/y/z"))
console.log("find")
console.log(Array.from(find(tree, "classes/name")))
console.log(Array.from(find(tree, "deep/example/nested")))
console.log(Array.from(find(tree, "x/y/z")))
Strictly spoken, body.data.area[0].NY is not in the path of body, sorry. body.data.area is in the path. For the object without body.data.area as array here's a solution. If you want to include objects within arrays as part of an objects path, the solution will be more complex
const spath = "data/area/NY";
const spath2 = "data/message/details/lastGreeting";
const notPath = "data/message/details/firstGreeting";
const body = {
data: {
school: 'yaba',
age: 'tolu',
names : ['darious'],
area: {
NY: true,
BG: true
},
message: {
content: 'now',
details: {
lastGreeting: true
}
}
},
time: 'UTC',
class: 'Finals'
};
console.log(`${spath} exists? ${ exists(body, spath) && `yep` || `nope`}`);
console.log(`${spath2} exists? ${ exists(body, spath2) && `yep` || `nope`}`);
console.log(`${notPath} exists? ${ exists(body, notPath) && `yep` || `nope`}`);
function exists(obj, path) {
const pathIterable = path.split("/");
while (pathIterable.length) {
const current = pathIterable.shift();
// no path left and exists: true
if (pathIterable.length < 1 && current in obj) { return true; }
// up to now exists, path continues: recurse
if (current in obj) { return exists(obj[current], pathIterable.join("/")); }
}
// no solution found: false
return false;
}
You can check this solution. Will also check for array of objects.
const body = {
data: {
school: 'yaba',
age: 'tolu',
message: {
content: 'now',
details: {
lastGreeting: true,
},
},
area: [
{
NY: true,
BG: true,
},
],
},
time: 'UTC',
class: 'Finals',
};
const spath1 = 'data/message';
const spath2 = 'data/message/details/lastGreeting';
const spath3 = 'data/area/NY';
const spath4 = 'data/area/NY/Test';
console.log(`${spath1}: `, isPathExists(body, spath1.split('/'), 0));
console.log(`${spath2}: `, isPathExists(body, spath2.split('/'), 0));
console.log(`${spath3}: `, isPathExists(body, spath3.split('/'), 0));
console.log(`${spath4}: `, isPathExists(body, spath4.split('/'), 0));
function isPathExists(data, pathArr, i) {
const key = pathArr[i];
if (Array.isArray(data)) {
for (let value of data) {
if (isObject(value)) return isPathExists(value, pathArr, i);
}
} else if (data.hasOwnProperty(key)) {
if (key === pathArr[pathArr.length - 1]) return true;
return isPathExists(data[key], pathArr, i + 1);
} else return false;
return true;
}
function isObject(a) {
return !!a && a.constructor === Object;
}
With help of a colleague we eventually came up with something simple and easy to comprehend that really suits our needs. The answer with the yield implementation solves the problem but we were looking for something someone can read and easily understand in the codebase. We wanted to be able to check if the path exists in the object as well as get the value.
So we added a third param called returnValue - by default it will always return the value. If we don't want it to do that, we can set the value of the return value to false and the function will check if the path am looking exists, if it does, it will return true else return false
This is what we finally came up with
const find = (path, data) => {
if (Array.isArray(data)) {
data = data[0];
}
for (const item in data) {
if (item === path) {
return data[item];
}
}
return null;
};
const findPath = (fullPath, fullData, returnValue = true) => {
const pathArray = fullPath.split('/');
let findResult = fullData;
for (const pathItem of pathArray) {
findResult = find(pathItem, findResult);
if (!findResult) {
if (!returnValue) return false;
return null;
}
}
if (!returnValue) return true;
return findResult;
};
const body = {
name: 'mike',
email: 1,
data: {
school: [
{
testing: 123
}
]
}
}
console.log(findPath('data/school/testing', body))
Here is a clean, iterative solution using object-scan
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.1.2/lib/index.min.js';
const data1 = { data: { school: 'yaba', age: 'tolu', message: 'true' }, time: 'UTC', class: 'Finals' };
const data2 = { data: { school: 'yaba', age: 'tolu', message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' };
const data3 = { data: { school: 'yaba', age: 'tolu', names: ['darious'], area: [{ NY: true, BG: true }], message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' };
const path1 = 'data/message';
const path2 = 'data/message/details/lastGreeting';
const path3 = 'data/area/NY';
const exists = (data, n) => objectScan([n.replace(/\//g, '.')], {
useArraySelector: false,
rtn: 'bool',
abort: true
})(data);
console.log(exists(data1, path1));
// => true
console.log(exists(data2, path2));
// => true
console.log(exists(data3, path3));
// => true
</script>
Disclaimer: I'm the author of object-scan
I have two Object of array like following
var a={
firstObj:
[
{
Ref: "5ca3cd6aefbc9f1782b5db53",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
}
]
};
var b={
secondObj: [
{
_id: "5ca3cd6aefbc9f1782b5db53"
},
{
_id: "5ca3cdc6efbc9f1782b5db5c"
},
]
}
I want to check if a.firstObj has matching Ref to b.secondObj._id if it has then I am trying to assign into firstObj element to matching b.secondObj.new but somehow _id is not matching
I am trying through map
a.firstObj.map(item =>
b.secondObj.map((_item, index) => {
console.log(_item._id.toString());
console.log(item.Ref.toString());
if (_item._id == item.Ref) {
b.secondObj[index].new = item;
}
})
);
AFAI tested... yes it's matching. Take a look:
var a={
firstObj:
[
{
Ref: "5ca3cd6aefbc9f1782b5db53",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
}
]
};
var b={
secondObj: [
{
_id: "5ca3cd6aefbc9f1782b5db53"
},
{
_id: "5ca3cdc6efbc9f1782b5db5c"
},
]
}
a.firstObj.map(item =>
b.secondObj.map((_item, index) => {
//console.log(_item._id.toString());
//console.log(item.Ref.toString());
if (_item._id == item.Ref) {
b.secondObj[index].new = item;
}
})
);
console.log(b)
You probably missed something. If there's anything else you want to know, please reformulate and I'll change the answer.
Please understand that with this code, as a object has many equal Ref elements, the association will be with the last Ref.
You can do a map with an inner reduce:
var a={
firstObj:
[
{
Ref: "5ca3cd6aefbc9f1782b5db53",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
}
]
};
var b={
secondObj: [
{
_id: "5ca3cd6aefbc9f1782b5db53"
},
{
_id: "5ca3cdc6efbc9f1782b5db5c"
},
]
}
function mergeByEqProp (propA, propB, listA, listB) {
return listB.map(function (b) {
return listA.reduce(function (acc, a) {
if (!acc && a[propA] === b[propB]) {
return Object.assign({}, b, {new: a});
}
return acc;
}, null);
});
}
console.log(mergeByEqProp('Ref', '_id', a.firstObj, b.secondObj));
It first maps over the second list and then reduces the items in the first list until it finds a match. As soon as it finds a match, it always returns that. I added that functionality because it looks like there is some repetition in a.firstObj (have a close look at the Ref properties). In case that's fine, just change the if (!acc && a[propA] === b[propB]) { part to if (a[propA] === b[propB]) {.
The Object.assign part deserves a bit of explanation as well:
First of all, in case you don't work in an ES6/ES2015+ environment, you need another function as a replacement – $.extend for example works well. Or write your own, like:
function assign (a /*, ...rest */) {
var r = Array.prototype.slice.call(arguments, 1);
return r.reduce(function (acc, x) {
for (var y in x) {
if (x.hasOwnProperty(y)) { acc[y] = x[y]; }
}
return acc;
}, a);
}
The other thing to explain is the empty {}: It is merely there to create a copy first and alter that copy instead of the original item. If that doesn't bother you, just use Object.assign(b, a) (or your own assign(b, a) respectively).
/* This code will identify all the items that match with Ref id and then assign them into new property inside the secondObj as an array */
<script type="text/javascript">
var a={
firstObj:
[
{
Ref: "5ca3cd6aefbc9f1782b5db53",
status: "abcd"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "efgh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hijk"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "lmno"
}
]
};
var b={
secondObj: [
{
_id: "5ca3cd6aefbc9f1782b5db53"
},
{
_id: "5ca3cdc6efbc9f1782b5db5c"
},
]
}
let equality = [];
for(let i=0; i<b.secondObj.length ; i++){
let itemB = b.secondObj[i];
equality.push(a.firstObj.filter(itemA => itemA.Ref == itemB._id)) ;
}
equality.map((value, index) => b.secondObj[index].new = value); //<=== in case if there are more than one equal items in firstObj
//equality.map((value, index) => b.secondObj[index].new = value[0]); <== only asign one item to 'new'
console.log(b);
</script>
I try to write a function in JavaScript which filter an array by a selected property (an value).
But it works for 2 level only I do not understand what do I missing.
The data I want to filter:
var data = [
{
name: "john_pc",
children: [
{
name: "sabrina_pc",
children: [
{
name: "sabrina_pc"
},
{
name: "john_pc"
}
]
},
{
name: "john_pc"
}
]
},
{
name: "sabrina_pc"
}
]
The childrenFilter funciton :
const childrenFilter = (childrenData, filters) => {
let filteredData = childrenData.filter(item => {
for (var property in filters) {
var optionalValues = filters[property];
var value = item[property];
if (item.children) {
item.children = childrenFilter(item.children, filters);
}
let hasValue = value == optionalValues;
if (hasValue) {
return true;
}
return false;
}
return false;
}, this);
return filteredData;
}
Calling the function:
As you can see the 'childrenFilter' get an object which the key is property in the data and the key is value I want to keep.
let result = childrenFilter(data, {
"name": "a1"
});
console.log(JSON.stringify(result, null, 2))
The wanted result :
[
{
"name": "john_pc",
"children": [
{
"name": "sabrina_pc",
"children": [
{
"name": "john_pc"
}
]
},
{
"name": "john_pc"
}
]
}
]
Your filter function does not take into account whether or not children elements match the pattern, therefore even though some child elements of the object match the pattern, the object itself is being filtered out.
Here is the explanation:
{
name: "a2", // does not match filter {name:'a1} so is removed alongside child objects
children: [ // gets removed with parent object
{
name: "a2"
},
{
name: "a1"
}
]
}
This should produce the desired output:
const childrenFilter = (childrenData, filters) => {
let filteredData = childrenData.filter(item => {
for (var property in filters) {
var optionalValues = filters[property];
var value = item[property];
if (item.children) {
item.children = childrenFilter(item.children, filters);
}
let hasValue = value == optionalValues;
if (hasValue || item.children.length) { // include item when children mathes the pattern
return true;
}
return false;
}
return false;
}, this);
return filteredData;
}
You could build new array for each step of filtering, beginning from the leaves and check if this contains the wanted value.
This approach generates new objects and does not mutate the original data.
function filter(array, filters) {
return array.reduce((r, o) => {
var children = filter(o.children || [], filters);
return children || Object.entries(filters).every(([k, v]) => o[k] === v)
? (r || []).concat(Object.assign({}, o, children && { children }))
: r;
}, undefined);
}
var data = [{ name: "a1", children: [{ name: "a2", children: [{ name: "a2" }, { name: "a1" }] }, { name: "a1" }] }, { name: "b1" }];
console.log(filter(data, { name: "a1" }));
.as-console-wrapper { max-height: 100% !important; top: 0; }