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
Related
This question already has answers here:
foreach return object property javascript
(4 answers)
Closed 1 year ago.
var OBJ = {
code: 42,
item1: [{
id: 1,
name: 'foo'
}, {
id: 2,
city: 'NY'
name: 'bar'
}],
thing: [{
id: 14,
name: 'foo'
}, {
id: 5,
street: 'E43'
name: 'pub'
}]
};
Javascript object(OBJ)
I need a method that returns VALUE of KEY I pass as an argument if KEY is not present in OBJ method should return undefined
getKeyValueFromObject(OBJ , 'street') // should return 'E43'
getKeyValueFromObject(OBJ , 'dog') // should return undefined
I tried this(not working)
getKeyValueFromObject(obj: any, search: string) {
const notFound = {};
Object.keys(obj).forEach(key => {
if (key !== null && key !== undefined && !(obj[key] === undefined || obj[key] === null)) {
if (key === search) {
return obj[key];
} else if (obj[key].constructor === {}.constructor) {
const result = this.getKeyValueFromObject(obj[key], search);
if (result !== notFound) return result;
} else if (Array.isArray(obj[key])) {
obj[key].forEach(element => {
const result = this.getKeyValueFromObject(element, search);
if (result !== notFound) return result;
});
}
}
});
return {};
}
Your code was almost working, but the return statements in your forEach code were not working as you expected. Look here: Grab the return value and get out of forEach in JavaScript?. Instead I used a variable resultKey to store a match.
There were some commas missing in your OBJ json, but I think that was not the main problem.
var OBJ = {
code: 42,
item1: [{
id: 1,
name: 'foo'
}, {
id: 2,
city: 'NY',
name: 'bar'
}],
thing: [{
id: 14,
name: "foo"
}, {
id: 5,
street: "E43",
name: "pub"
}]
};
function getKeyValueFromObject(obj, search) {
let resultKey = undefined;
const notFound = {};
Object.keys(obj).forEach(key => {
if (key !== null && key !== undefined && !(obj[key] === undefined || obj[key] === null)) {
if (key === search) {
resultKey = obj[key];
} else if (obj[key].constructor === {}.constructor) {
const result = this.getKeyValueFromObject(obj[key], search);
if (result !== notFound) resultKey = result;
} else if (Array.isArray(obj[key])) {
obj[key].forEach(element => {
const result = this.getKeyValueFromObject(element, search);
if (result !== notFound) resultKey = result;
});
}
}
return;
});
return resultKey;
}
console.log(getKeyValueFromObject(OBJ, "street"));
console.log(getKeyValueFromObject(OBJ , "dog"));
I have some attributes from a nested object that is inside the parent object but I would like to merge nested object with the parent object to be flatten.
Original object:
enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'string'
}
expected flatten object:
user: {
id: 'string',
name: 'string',
finished: 'boolean',
path: 'string'
}
You can recursively build object any number of nested objects. So, this function is not your case dependent:
var enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'boolean'
}
var enrollment2 = {
user: {
id: 'string',
name: 'string'
},
test: {
test1: {
test2: {
val0:'val0',
test4: { //3rd level nested object for example
val1: 'val1',
val2: 'val2'
}
}
}
},
finished: 'boolean',
path: 'boolean'
}
const flat = (obj, out) => {
Object.keys(obj).forEach(key => {
if (typeof obj[key] == 'object') {
out = flat(obj[key], out) //recursively call for nesteds
} else {
out[key] = obj[key] //direct assign for values
}
})
return out
}
console.log(flat(enrollment, {}))
console.log(flat(enrollment2, {}))
I needed something that avoids rewriting keys with the same name that were in different levels in the original object. So I wrote the following:
const flattenObject = (obj, parentKey = '') => {
if (parentKey !== '') parentKey += '.';
let flattened = {};
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(flattened, flattenObject(obj[key], parentKey + key))
} else {
flattened[parentKey + key] = obj[key]
}
})
return flattened;
}
var test = {
foo: 'bar',
some: 'thing',
father: {
son1: 'son1 value',
son2: {
grandchild: 'grandchild value',
duplicatedKey: 'note this is also used in first level',
},
},
duplicatedKey: 'note this is also used inside son2',
}
let flat = flattenObject(test);
console.log(flat);
// how to access the flattened keys:
let a = flat['father.son2.grandchild'];
console.log(a);
Also checks if the object is null, as I was having some problems with that in my usage.
Here's a quick and dirty way to flatten your object:
var enrollment = {
user: {
id: 'string',
name: 'string',
},
fineshed: true,
path: false,
};
var user = Object.assign(enrollment.user);
user.fineshed = enrollment.fineshed;
user.path = enrollment.path;
For a generic method with a couple of caveats of no shared key names and only flattening 1 level of depth:
var enrollment = {
user: {
id: 'string',
name: 'string',
},
fineshed: true,
path: false,
};
const flatten = (object) => {
let value = {};
for (var property in object) {
if (typeof object[property] === 'object') {
for (var p in object[property]) {
value[p] = object[property][p];
}
} else {
value[property] = object[property];
}
}
return value;
};
let user = flatten(enrollment);
console.log(user);
using recursion and reduce.
note that if value itself is an array containing objects, you might want add another check like !Array.isArray(value) depending on your case
function flatObj(obj) {
return Object.entries(obj).reduce(
(flatted, [key, value]) =>
typeof value == "object"
? { ...flatted, ...flatObj(value) }
: { ...flatted, [key]: value },
{}
);
}
Just want a single Object:
const enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'boolean'
}
function propsToUser(enrollObj){
const u = {...enrollObj.user};
for(let i in enrollObj){
if(i !== 'user')u[i] = enrollObj[i];
}
return u;
}
const user = propsToUser(enrollment);
console.log(user);
Below code snippet takes nested input object like this :
{
name:'Namig',
surname:'Hajiyev',
address:{
city:'Sumgait',
country:'Azerbaijan',
geo: {
lat:'40.5897200',
long:'49.6686100'
}
}
}
and returns result flattened object like this:
{
"name": "Namig",
"surname": "Hajiyev",
"address.city": "Sumgait",
"address.country": "Azerbaijan",
"address.geo.lat": "40.5897200",
"address.geo.long": "49.6686100"
}
Here is my code :
function flattenObject(obj, newObj, prefix) {
newObj = newObj || {};
prefix = prefix || "";
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
const type = typeof obj[key];
const newKey = !!prefix ? prefix + "." + key : key;
if (type === "string") {
newObj[newKey] = obj[key];
}
else if (type === "object") {
flattenObject(obj[key], newObj, newKey);
}
}
}
return newObj;
}
var obj = {
name:'Namig',
surname:'Hajiyev',
address:{
city:'Sumgait',
country:'Azerbaijan',
geo: {
lat:'40.5897200',
long:'49.6686100'
}
}
}
console.log(flattenObject(obj));
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"]));
I have a sanitizeStr() function that I need to run on EVERY property/subproperty that exists in an object like the one below:
const data = {
info: 'schools',
schools: [
{ name: 'Johnson Elementary', type: 'elementary' },
{ name: 'Iselin Middle School', type: 'middle' }
],
bestStudent: {
name: 'John',
grade: 'sixth'
}
};
The issue is that for every single one of these properties, they may or may not exist. Right now, I'm having to do multiple if checks for each property and manually running the function:
// Is there a better way to do this rather than what I have here:
if (data.info) {
data.info = sanitizeStr(data.info);
}
if (data.bestStudent) {
if (data.bestStudent.name) {
data.bestStudent.name = sanitizeStr(data.bestStudent.name);
}
if (data.bestStudent.grade) {
data.bestStudent.grade = sanitizeStr(data.bestStudent.grade);
}
}
if (data.schools) {
data.schools.forEach((school, i) => {
if (school.name) {
data.schools[i].name = sanitizeStr(school.name);
}
if (school.grade) {
data.schools[i].grade = sanitizeStr(school.grade);
}
});
}
If anyone knows of a cleaner/less manual way of doing this, it would be appreciated.
You could use an iterative and recursive approach for objects and call the function for non objects only.
function sanitizeStr(s) {
return '#' + s;
}
function iterAll(object) {
Object.keys(object).forEach(function (k) {
if (object[k] && typeof object[k] === 'object') {
iterAll(object[k]);
return;
}
object[k] = sanitizeStr(object[k]);
})
}
var data = { info: 'schools', schools: [{ name: 'Johnson Elementary', type: 'elementary' }, { name: 'Iselin Middle School', type: 'middle' }], bestStudent: { name: 'John', grade: 'sixth' } };
iterAll(data);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You must me looking for this
const sanitizeObject = (obj, callBack, isClone = false) => {
let tempObj = obj;
if(typeof callBack === 'function' && (typeof tempObj === 'string' || typeof tempObj === 'number')){
return callBack(tempObj)
}else if(typeof tempObj === 'object' && tempObj !== null){
tempObj = isClone ? (Array.isArray(tempObj) ? [...tempObj] : {...tempObj}) : tempObj;
Object.keys(tempObj).forEach(objKey => {
const valueOfobject = tempObj[objKey]
if(typeof valueOfobject === 'string' || typeof valueOfobject === 'number'){
tempObj[objKey] = callBack(tempObj[objKey])
}else {
tempObj[objKey] = sanitizeObject(valueOfobject, callBack, isClone)
}
})
}
return tempObj;
}
const data = {
test1: {
test2: [{
property: "any string",
property2: null
}]}
}
console.log(sanitizeObject(data, function (stringValue){
return stringValue + " apend"
}))
I have a nested object which consists of :
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
So if I have a string or an array of values that match any of the values including the nested object values in the collection, it will return true. I have tried using _.includes of lodash but I don't know how I can iterate through the nested object.
_.includes(obj.department, 'Operations')
What I am trying to do is more like
_.includes(obj, ['Stephen', 'Operations']) // return true
Use recursion with Array#some to check if the value exists. Array#some returns immediately when the result of the predicate is true.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function recursiveIncludes(obj) {
var values = [].slice.call(arguments, 1);
return Object.keys(obj).some(function(key) {
var current = obj[key];
if(values.indexOf(current) !== -1) {
return true;
}
if(typeof current === 'object' && current !== null) {
return recursiveIncludes.apply(null, [current].concat(values));
}
return false;
});
}
console.log('Operations: ', recursiveIncludes(obj, 'Operations'));
console.log('Moses, Stephen: ', recursiveIncludes(obj, 'Moses', 'Stephen'));
console.log('Moses, 58: ', recursiveIncludes(obj, 'Moses', 58));
Here is a recursive approach to it. It extracts all the properties of an object (the leaf properties) into an array. You can then call _.includes() on it.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function objToArray(obj) {
var result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result = result.concat(toArray(value));
}
else {
result.push(value);
}
}
return result;
}
_.includes(objToArray(obj), ['Stephen', 'Operations'])
Given an array of strings, this will check and see if the property exists recursively.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function hasValues(obj, props)
{
var keys = Object.keys(obj);
for(var i = 0; i < keys.length; i++){
var item = obj[keys[i]];
// If the item is a string or number, do a comparison
if(typeof item === "string" || typeof item === "number"){
var idx = props.indexOf(item);
if(idx >= 0) props.splice(idx, 1);
// If it's an object then search the object recursively
} else if(typeof item === "object"){
hasValues(item, props);
}
}
return props.length === 0;
}
console.log(hasValues(obj, ['Stephen', 'Operations']))
console.log(hasValues(obj, [18, 1]))
console.log(hasValues(obj, [18, '13lkj4']))
You can use flatMap as a mechanism to flatten all the values taken from recursively using map inside the flatMap callback function. After obtaining all the values, we use difference to get the difference between all the values between the object and the values. Lastly, we check the resulting difference if it is empty using isEmpty.
function includesDeep(object, values) {
return _(obj)
.flatMap(function cb(v) { return _.isObject(v)? _.map(v, cb): v; })
.thru(_.partial(_.difference, values))
.isEmpty();
}
var result = includesDeep(obj, ['Stephen', 'Operations']);
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
};
function includesDeep(object, values) {
return _(obj)
.flatMap(function cb(v) { return _.isObject(v)? _.map(v, cb): v; })
.thru(_.partial(_.difference, values))
.isEmpty();
}
var result = includesDeep(obj, ['Stephen', 'Operations']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
the tests shows that you can't check multi-values & value in-depth with lodash includes,so you must write a function for yourself.
Your solution
describe('includes', () => {
var value = {
foo: 'bar',
fuzz: 'buzz',
value2: {
key: 'value'
}
};
function includes(collection, values) {
return [].concat(values).every((value) => {
return Object.keys(collection).some((key) => {
let it = collection[key];
return typeof it == 'object' ? includes(it, value) : _.includes(it, value);
})
});
}
it('check single value', () => {
expect(includes(value, 'bar')).toBe(true);
expect(includes(value, 'baz')).toBe(false);
});
it('check multi values', () => {
expect(includes(value, ['bar', 'buzz'])).toBe(true);
expect(includes(value, ['baz', 'buzz'])).toBe(false);
});
it('check value in depth', () => {
expect(includes(value, 'value')).toBe(true);
expect(includes(value, 'no-exists')).toBe(false);
});
});
Test
describe('includes', () => {
var value = {
foo: 'bar',
fuzz: 'buzz',
value2: {
key: 'value'
}
};
it('check single value', () => {
expect(_.includes(value, 'bar')).toBe(true);
expect(_.includes(value, 'baz')).toBe(false);
});
it('check multi values', () => {
expect(_.includes(value, ['bar', 'buzz'])).toBe(false);
expect(_.includes(value, ['baz', 'buzz'])).toBe(false);
});
it('check value in depth', () => {
expect(_.includes(value, 'value')).toBe(false);
});
});
function includes(collection, values) {
return [].concat(values).every(function (value) {
return Object.keys(collection).some(function (key) {
var it = collection[key];
return (typeof it == 'object') ? includes(it, value) : _.includes(it, value);
});
});
}
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
};
var tests=[
"Operations",
"Non-Existing Value",
['Stephen', 'Operations'],
['Stephen', 'Non-Existing Value'],
];
tests.forEach(function(test){
console.log("includes(obj,"+JSON.stringify(test)+") => "+ includes(obj,test));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>