I have an array of complicated objects and arrays in javascript such as:
var array = [
{ "simpleProp": "some value" },
{ "booleanProp": false },
{
"arrayProp": [
{ "prop1": "value1" },
{
"prop2": {
"prop22": "value22",
"prop23": "value23"
}
},
{ "prop3": "value3" },
{ "booleanProp": true }
]
}
];
I have to know if there is a property with defined value in my array, such as:
function some(array, property, value) {
//some logic here
// return boolean
};
That is, for my source array the result of this:
var result = some(array, "booleanProp", true)
- must be TRUE.
I tried to use lodash function _.some(), but it returns false for my array, it appears _.some() can't find deeply nested properties.
It would be very cool if the function may support complicated object as source, not only array.
I'd appreciate any help, thanks.
You could use an iterative and recursive approach by checking the actual object and if the value is an object iterate the object's keys.
function some(object, property, value) {
return object[property] === value || Object.keys(object).some(function (k) {
return object[k] && typeof object[k] === 'object' && some(object[k], property, value);
});
}
var data = [{ simpleProp: "some value" }, { booleanProp: false }, { arrayProp: [{ prop1: "value1" }, { prop2: { prop22: "value22", prop23: "value23" } }, { prop3: "value3" }, { booleanProp: true }] }];
console.log(some(data, 'booleanProp', true)); // true
console.log(some(data, 'foo', 42)); // false
Above Solution is great but it is not working for Array.
So I've Modified it little bit and now it is working for both Arrays & normal properties. Even In Arrays element's placement can be anything.
const data = {
"names": [
{
"name": {
'homename': 'Raju',
'academisName': 'Rajpal',
'callingName': ['Raj', 'Rajpal', 'Raju']
},
"defaultName": "Raj"
}]
}
Code for Array:
const some = (object, property, value) => {
return _.isArray(value) && _.isEqual(_.sortBy(object[property]), _.sortBy(value)) || object[property] === value || Object.keys(object).some(function (k) {
return object[k] && typeof object[k] === 'object' && some(object[k], property, value);
});
}
const data = {
"names": [{
"name": {
'homename': 'Raju',
'academisName': 'Rajpal',
'callingName': ['Raj', 'Rajpal', 'Raju']
},
"defaultName": "Raj"
}]
}
const some = (object, property, value) => {
return _.isArray(value) && _.isEqual(_.sortBy(object[property]), _.sortBy(value)) || object[property] === value || Object.keys(object).some(function(k) {
return object[k] && typeof object[k] === 'object' && some(object[k], property, value);
});
}
console.log('Result 1', some(data, 'callingName', ["Raj", "Rajpal", "Raju"]));
console.log('Result 2', some(data, 'callingName', ["Rajpal", "Raj", "Raju"]));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.js"></script>
Note: that value.sort() will mutate the arrays so I've used _.sortBy(value), same for object[property]
console.log(some(data, 'callingName', ["Raj", "Rajpal", "Raju"]));
console.log(some(data, 'callingName', ["Rajpal", "Raj", "Raju"]));
Related
I am looking for an efficient way of sorting the API response which is array of Objects. This Array has many fields and I just want to sort only few of them.
The Array looks like this
result = {type: Array(), status: Array(), nature: Array(), health: Array(), fitness: Array(), wealth: Array()}
and Array have name and value property like {name:"", value:""}
so let's say I just need to sort type, status, and nature out of this result. The thing that I have tried now looks like this which juts sorts one of the records.
const typeName = "type"
if(result[typeName]){
result[typeName] = sortingFunction(result[typeName], "name")
}
Now I need to sort other fields as well and also for few fields I need to sort on the basis of "value" property as well.
So please let me know if you have any efficient way of doing this.
You could create a sort function which can sort the given input object for the given keys.
I have create a sample function for sorting.
This function has two parameters.
First the object which needs to be sorted
Second option, you can pass the option for sort.
a. sortBy: Name of the property on which the function will perform the sort .
b. sortKeys: Array | String, the keys/key of the object which need to be sorted.
Function:
function sortObject(input, options = {}) {
if (!options)
return;
let keys = options.sortKeys;
let sortBy = options.sortby
if (!sortBy) {
console.error("sort by option is not defiend");
return;
}
if (!keys) {
console.error("sort keys are not defiend");
return;
}
if (Array.isArray(keys) && keys.length > 0) {
keys.forEach(item => sortObjectByKey(item, sortBy));
return;
}
if (typeof keys === "string" && keys) {
sortObjectByKey(keys, sortBy);
return;
}
function sortObjectByKey(sortKey, sortBy) {
input[sortKey].sort(function (a, b) {
let _a = (typeof a[sortBy] === "string") ? a[sortBy].toLowerCase() : a[sortBy];
let _b = (typeof b[sortBy] === "string") ? b[sortBy].toLowerCase() : b[sortBy];
if (_a < _b)
return -1
if (_a > _b)
return 1
return 0
});
}
}
Example:
//sortObject(sampleObject, { sortby: ["name", "value"], sortKeys: ["status", "type"] });
function sortObject(input, options = {}) {
if (!options)
return;
let keys = options.sortKeys;
let sortBy = options.sortby
if (!sortBy) {
console.error("sort by option is not defiend");
return;
}
if (!keys) {
console.error("sort keys are not defiend");
return;
}
if (Array.isArray(keys) && keys.length > 0) {
keys.forEach(item => sortObjectByKey(item, sortBy));
return;
}
if (typeof keys === "string" && keys) {
sortObjectByKey(keys, sortBy);
return;
}
function sortObjectByKey(sortKey, sortBy) {
input[sortKey].sort(function (a, b) {
let _a = (typeof a[sortBy] === "string") ? a[sortBy].toLowerCase() : a[sortBy];
let _b = (typeof b[sortBy] === "string") ? b[sortBy].toLowerCase() : b[sortBy];
if (_a < _b)
return -1
if (_a > _b)
return 1
return 0
});
}
}
let sampleObject = {
type: [
{ name: "c", value: 4 },
{ name: "a", value: 2 },
{ name: "b", value: 1 },
{ name: "d", value: 3 },
],
status: [
{ name: "c", value: 25 },
{ name: "a", value: 25 },
{ name: "b", value: 25 },
{ name: "d", value: 25 },
],
nature: [
{ name: "c", value: 25 },
{ name: "a", value: 25 },
{ name: "b", value: 25 },
{ name: "d", value: 25 },
],
}
sortObject(sampleObject, { sortby: "value", sortKeys: ["type"] });
sortObject(sampleObject, { sortby: "name", sortKeys: ["status", "nature"] });
console.log(sampleObject)
One way is to translate the object of arrays into an array of objects, then merge it back after sorting.
const result = {
type: ['foo', 'bar', 'baz'],
status: [4, 3, 5],
nature: ['forest', 'animal', 'water'],
health: ['athlete', 'couch potato', 'dead'],
fitness: [200, 50, 60],
wealth: [5, 2, 99]
};
// 1. combine
const combined = result.type.map((_, index) => {
return Object.fromEntries(Object.keys(result).map(key => [key, result[key][index]]));
});
// 2. example sort by status
combined.sort((a, b) => a.status - b.status)
// 3. merge
combined.forEach((object, index) => {
for (const [key, value] of Object.entries(object)) {
result[key][index] = value
}
})
console.log(result);
I need to replace every time value in a nested object with a momentJS element of its value.
const input = {
data: {
sub1: {
time: 1578857603218
}
sub2: {
some: 'thing,
foo: [{
value: 123,
time: 1578857603218
}]
}
}
}
Right now my code looks very ugly, as I'm doing this manually as there are specific fields with an optional time value.
if (data && data.sub2 && data.sub2.foo && data.sub2.foo[0].time) {
data.sub2.foo[0].time = moment(data.sub2.foo[0].time).toDate()
}
To do this in a more dynamic way, I see two options:
Pass something like an array/map with all optional time fields and replace them with a loop
Is there a better way to replace my if conditions to go through all relacing time fields?
Iterate through all keys
But this would not work for nested objects.
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// do stuff
}
}
If you know the key value pair, then what you show as already in use is exactly what you should use. It is O(1), very quick, and essentially a single line.
Making that dynamic will require O(n) where n is the number of key value pairs, and will require several lines of code.
const renderTime = input => {
if (Array.isArray(input)) {
input.forEach(el => renderTime(el));
}
if (typeof input === 'object' && !!input) {
Object.keys(input).forEach(k => {
if (k === 'time') input[k] = 'moment(input[k]).toDate()';
else renderTime(input[k]);
});
}
};
const input = {
data: {
sub1: {
time: 1578857603218
},
sub2: {
some: 'thing',
foo: [{
value: 123,
time: 1578857603218
}]
}
}
};
renderTime(input);
console.log(input);
Whenever you want to deal with nested objects with an undetermined level of depth, think of recursivity
const setTime = (object, time) => {
for (let prop in object) {
if (!Object.prototype.hasOwnProperty.call(object, prop)) {
continue;
}
if (typeof (object[prop]) === 'object') {
setTime(object[prop], time);
}
if (prop === 'time') {
object.time = time;
}
}
return object;
};
const input = {
data: {
sub1: {
time: 1578857603218
},
sub2: {
some: 'thing',
foo: [{
value: 123,
time: 1578857603218
}]
}
}
}
setTime(input, 666);
Try this one:) The secret is recursive loop
const input = {
data: {
sub1: {
time: 1578857603218
},
sub2: {
some: 'thing',
foo: [{
value: 123,
time: 1578857603218
}]
}
}
};
function changeTime(obj) {
obj && Object.keys(obj).forEach(key => {
if(key === 'time') {
obj.time = moment(obj.time); // you have to use moment
}
if(typeof obj[key] === 'object'){
changeTime(obj[key]);
}
});
}
changeTime(input);
console.log(input);
This won't handle the case where the nested fields are in an array, but it should work for nested objects
function replaceNestedValues(obj, targetVal, nextVal) {
return Object.keys(obj).reduce((acc, key) => {
const value = obj[key];
if (typeof value === 'object' && value !== null) {
acc[key] = replaceNestedValues(value, targetVal, nextVal);
} else if (value === targetVal) {
acc[key] = nextVal;
} else {
acc[key] = value;
}
return acc;
}, {});
}
Example
const data = { a: { b: 10, c: null }, d: null };
replaceNestedValues(data, null, '');
// => { a: { b: 10, c: '' }, d: '' }
You can use this code to modify your property. The property can be present in deeply nested object or within array of objects.
foo(entry: any | any[]) {
if (Array.isArray(entry)) {
entry.forEach(ent => this.foo(ent));
} else if (typeof entry === 'object' && entry !== null) {
for (let [key, value] of Object.entries(entry)) {
if (typeof value === 'object' && value !== null) {
this.foo(value);
} else if (Array.isArray(value)) {
this.foo(value);
}
if (key === "time") {
entry[key] = "changed"; // modified value
}
}
return entry;
}
}
this.foo(data); // your object
I am trying to compare 2 objects using deep comparison and while comparison i want to ignore some properties.
My comparison is successful when I have those ignore properties on both the side of object.
But I am getting problem when I have 1 property missing in 2nd object which I want to ignore.
In my objA and objB, I want to ignore isParent and location property but as I don't have location property in objB, my object comparison is failing.
But I don't understand why I am getting false as I have specified location property to ignore.
var objA = {
isParent: true,
foo: {
location: "abc",
bar: "foobar"
}
};
var objB = {
isParent: false,
foo: {
bar: "foobar"
}
};
var comparator = function(left, right, key) {
if (key === 'isParent' || key === 'location') return true;//ignore isParent and location property while comparing 2 object
else return undefined;
}
var isEqual = _.isEqualWith(objA, objB, comparator);
console.log(isEqual); // true
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Use the omit function to ignore unwanted properties then compare
var objA = {
isParent: true,
foo: {
location: "abc",
bar: "foobar"
}
};
var objB = {
isParent: false,
foo: {
bar: "foobar"
}
};
var isEqual = _.isEqual(
_.omit(objA, ['isParent', 'foo.location']),
_.omit(objB, ['isParent', 'foo.location'])
);
console.log(isEqual); // true
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
You can write your own compare function:
Logic:
Create a function that takes 2 objects that will be compared and an array(ignoreKeys) of keys that are to be ignored.
Get all keys from both object, merge them and then filter them into a new variable (say keys).
If the current key exists in keys, ignore it.
If the current key exists in ignoreKeys, ignore it
Else push it.
Now loop over these keys and check for comparison:
If current value is of type 'object', use recursion and start the process again.
Else, compare the values and return the comparison.
Since this has to be done for all the keys in keys, you can use Array.every.
Sample
function compareObject(obj1, obj2, ignoreProps){
var temp = Object.keys(obj1).concat(Object.keys(obj2)).sort();
var keys = temp.reduce(function(p,c) {
if(p.indexOf(c) < 0 && ignoreProps.indexOf(c) < 0) {
p.push(c);
}
return p;
}, []);
return keys.every(function(key){
var t1 = typeof(obj1[key])
var t2 = typeof(obj2[key])
if(t1 === t1) {
switch(t1) {
case 'object':
if(obj1[key] !== null && obj2[key] !== null)
return compareObject(obj1[key], obj2[key], ignoreProps);
else
return obj1[key] === obj2[key];
default: return obj1[key] === obj2[key];
}
}
})
}
var objA = {
isParent: true,
foo: {
location: "abc",
bar: "foobar",
test: {
location: 'bla',
test1: {
location: 'bla bla',
value: null
}
}
}
};
var objB = {
isParent: false,
foo: {
bar: "foobar",
test: {
location: 'new',
test1: {
location: 'new new',
value: null
}
}
}
};
var ignoreProperties = ['isParent', 'location'];
console.log(compareObject(objA, objB, ignoreProperties));
You could take all keys of the given objects and iterate and check if either
is a key of a no value check (ignore),
has same values or
both values are truthy and objects and the call of check returns a truthy value.
The keys of the properties to ignore are collected in an object.
function check(o, p) {
var keys = [...new Set(Object.keys(o).concat(Object.keys(p)))];
return keys.every(k => noValueCheck[k]
|| o[k] === p[k]
|| o[k] && p[k] && typeof o[k] === 'object' && typeof p[k] === 'object' && check(o[k], p[k])
);
}
var noValueCheck = { isParent: true, location: true },
objA = { isParent: true, foo: { location: "abc", bar: "foobar" } },
objB = { isParent: false, foo: { bar: "foobar" } };
console.log(check(objA, objB));
Let's say I have nested objects, like:
var obj = {
"items":[
{
"name":"Item 1",
"value": "500",
"options": [{...},{...}]
},
{
"name":"Item 2",
"value": "300",
"options": [{...},{...}]
}
],
"name": "Category",
"options": [{...},{...}]
};
I want to remove the options property from any level deep from all the objects. Objects can be nested within objects, and arrays as well.
We're currently using Lodash in the project, but I'm curious about any solutions.
There is no straight forward way to achieve this, however you can use this below function to remove a key from JSON.
function filterObject(obj, key) {
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
filterObject(obj[i], key);
} else if (i == key) {
delete obj[key];
}
}
return obj;
}
and use it like
var newObject = filterObject(old_json, "option");
Modifying the above solution, To delete "dataID" which appears multiple times in my JSON . mentioned below code works fine.
var candidate = {
"__dataID__": "Y2FuZGlkYXRlOjkuOTI3NDE5MDExMDU0Mjc2",
"identity": {
"__dataID__": "aWRlbnRpdHk6NjRmcDR2cnhneGE3NGNoZA==",
"name": "Sumanth Suvarnas"
},
};
candidate = removeProp(candidate, "__dataID__")
console.log(JSON.stringify(candidate, undefined, 2));
function removeProp(obj, propToDelete) {
for (var property in obj) {
if (typeof obj[property] == "object") {
delete obj.property
let newJsonData= this.removeProp(obj[property], propToDelete);
obj[property]= newJsonData
} else {
if (property === propToDelete) {
delete obj[property];
}
}
}
return obj
}
A little modification of void's answer that allows for deletion of propertise which are also objects
function filterObject(obj, key) {
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (i == key) {
delete obj[key];
} else if (typeof obj[i] == 'object') {
filterObject(obj[i], key);
}
}
return obj;
}
We now use object-scan for data processing tasks like this. It's very powerful once you wrap your head around it. Here is how you'd answer your questions
// const objectScan = require('object-scan');
const prune = (input) => objectScan(['**.options'], {
rtn: 'count',
filterFn: ({ parent, property }) => {
delete parent[property];
}
})(input);
const obj = { items: [{ name: 'Item 1', value: '500', options: [{}, {}] }, { name: 'Item 2', value: '300', options: [{}, {}] }], name: 'Category', options: [{}, {}] };
console.log(prune(obj));
// => 3
console.log(obj);
// => { items: [ { name: 'Item 1', value: '500' }, { name: 'Item 2', value: '300' } ], name: 'Category' }
.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 had similar issues. So, I developed the following library. Please see the source code of the library on GitHub, and you can download it using npm.
You can use the function removePropertiesDeeply together with isInitialized as below to remove all non-initialized properties (such as an empty object, empty array, empty string, or non-finite number).
const {removePropertiesDeeply, isInitialized} = require("#thedolphinos/utility4js");
const object = {
a: null,
b: "",
x: {
a: null,
b: ""
},
y: [],
z: [
null,
"",
{a: null, b: ""},
[[{a: {b: null, c: ""}}]],
"abc"
]
};
removePropertiesDeeply(object, (x) => !isInitialized(x));
console.log(JSON.stringify(object)); // See that the object becomes {"z":["abc"]}.
I had a similar issue and I got it resolved. I hope my solution might be helpful to someone.
I use Es6 ... spread operator to do a shallow copy of an object and made null to property I was not interested.
const newObject = {
...obj.items,
...obj.name,
options: null // option property will be null.
}
function omit(source) {
return isArray(source)
? source.map(omit)
: isObject(source)
? (({ options, ...rst }) => mapValues(rst, omit))(source)
: source;
}
as with lodash, that's an easy thing, also you can specify the key via an param like this
function omit(source, omitKey) {
return isArray(source)
? source.map(partialRight(omit,omitKey)))
: isObject(source)
? (({[omitKey]: _, ...rst }) => mapValues(rst, partialRight(omit,omitKey)))(source)
: source;
}
You can remove properties given a condition using following function:
// Warning: this function mutates original object
const removeProperties = (obj, condition = (key, value) => false) => {
for (var key in obj) {
const value = obj[key]
if (!obj.hasOwnProperty(key)) continue
if (typeof obj[key] === "object") {
removeProperties(obj[key], condition)
} else if (condition(key, value)) {
delete obj[key]
}
}
return obj
}
Examples:
// Remove all properties where key is equal to 'options'
removeProperties(someObject, (key, value) => key === 'options'))
// Remove all properties where key starts with 'ignore_'
removeProperties(someObject, (key, value) => key.startsWith('ignore_'))
// Remove all properties where value is null
removeProperties(someObject, (key, value) => value === null))
I have a JSON object that was returned from an XML to js function. This xml converter creates arrays for every entry even when they should be strings. I cannot modify this original function so therefore I would like to take my final json object, iterate through it, detect if a value is an array of length 1 and, if so, change that array to a string.
Original object:
var json = {
user: {
name: ["bob"],
email: ["bob#example.org"]
},
items: [{
name: ["Object 1"]
},{
name: ["Object 2"]
}]
}
Should become:
var json = {
user: {
name: "bob",
email: "bob#example.org"
},
items: [{
name: "Object 1"
},{
name: "Object 2"
}]
}
I have considered the reviver function but a) I would like to avoid going back to a string and b) I am not sure if that would even work as it will probably just feed me each array element individually.
This recursive function seems to work for this problem:
function simplify(obj) {
for (var k in obj) {
if (Object.prototype.toString.call(obj[k]) == '[object Array]' && obj[k].length == 1) {
obj[k] = obj[k][0];
}
else if (typeof obj[k] == 'object') {
obj[k] = simplify(obj[k]);
}
}
return obj;
}
simplify(json);
Demo: http://jsfiddle.net/xkz4W/
Here's a recursive way to do it:
function flattenArrays(data) {
function processItem(item) {
if (Array.isArray(item)) {
if (item.length === 1 && typeof item[0] === "string") {
data[prop] = item[0];
} else {
for (var i = 0; i < item.length; i++) {
processItem(item[i]);
}
}
} else if (typeof item === "object") {
flattenArrays(item);
}
}
for (var prop in data) {
processItem(data[prop]);
}
}
Working demo: http://jsfiddle.net/jfriend00/L5WKs/