extract object by property prefix - javascript

I tried to loop through a huge list of properties in object, but failed to extract properties that has the same prefix, I can't use object deletefunction because the list is huge, where is my mistake?
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
let b = {};
for(k in a){
const [key] = k.split('_');
if(key === 'hello') {
b = {...b[key], [key]:a[k]} //the problem is here, it gave me only hello_456: 'another value'
}
}
console.log(b);

Try using bracket notation
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
let b = {};
for (k in a) {
const [key] = k.split('_');
if (key === 'hello') {
b[k] = a[k];
}
}
console.log(b);
Using startsWith()
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
let b = {};
for (k in a) {
if (k.startsWith('hello_')) {
b[k] = a[k];
}
}
console.log(b);

Your key is hello for both hello_123 and hello_456, hence its overriding the old entry for hello key. you need unique keys. eg below.
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
let b = {};
for(k in a){
const [key] = k.split('_');
if(key === 'hello') {
//key is hello for both hello_123 and hello_456, hence its overriding
b[k] = a[k] //the problem is here, it gave me only hello_456: 'another value'
}
}
console.log(b);

Try this
const a = {
obj_abc: 123,
obj_def: 456,
hello_123: 123,
hello_456: 456
};
// filter out the keys that start with hello
var keys = Object.keys(a).filter(function(k) {
return k.indexOf("hello") === 0;
});
//to convert an array of filtered keys into an object of key-value pairs
var res = keys.reduce(function(matched, k) {
matched[k] = a[k];
return matched;
}, {});
console.log(res);

You can use entries, reduce for clean code. Same time you can create map of all key, later good to extract. See the example 2.
// Example 1
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
const result = Object.entries(a).reduce((map, [key, value]) => {
if (key.indexOf("hello_") === 0) map[key] = value
return map
}, {})
console.log(result);
// To collect all in once
// Example 2
const result2 = Object.entries(a).reduce((map, [key, value]) => {
const [k] = key.split("_")
if(!map[k]) map[k] = {}
map[k][key] = value
return map
}, {})
console.log(result2); // { obj: { obj_abc: true, obj_def: false }, hello: { hello_123: true, hello_456: 'another value' } }
console.log(result2["hello"]); // { hello_123: true, hello_456: 'another value' }
console.log(result2["obj"]); // { obj_abc: true, obj_def: false }

Please find my answer.
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: "another value"
};
let b = {};
for (key in a) {
let [text] = key.split("_");
if (!(text in b)) {
b[text] = { [key]: a[key] };
}
else {
Object.assign(b[text], { [key]: a[key] });
}
}
console.log(b);
OUTPUT
{
"obj": {
"obj_abc": true,
"obj_def": false
},
"hello": {
"hello_123": true,
"hello_456": "another value"
}
}

Related

How to flatten an object with nested objects in javascript

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

How to traverse a nested object based on a path in an array in javascript?

const obj = {
first: { second: { third: 'done'} },
hello: { world: { foo: { bar: 'wrong' } } },
second: { third: 'wrong'}
};
const arr = [ 'first', 'second', 'third' ];
function traverse(obj, arr) {
}
// output = 'done'
Given a first input as a nested object, and a second input as an array containing strings, what is the best way to traverse the nested object based on the path set by the array to output done?
You can reduce the array arr, changing the accumulator to a deeper object at each step.
const obj = {
first: { second: { third: 'done'} },
hello: { world: { foo: { bar: 'wrong' } } },
second: { third: 'wrong'}
};
const arr = [ 'first', 'second', 'third' ];
function traverse(obj, arr) {
return arr.reduce((acc, curr) => acc ? acc[curr] : undefined, obj);
}
console.log(traverse(obj, arr));
console.log(traverse(obj, ['hello', 'world', 'foo']));
console.log(traverse(obj, ['first', 'hello', 'world']));
You can use references
Loop over the array untill the last second last element
If the ref[key] is an object, change ref to ref[key]
else return Not Found
Check if the ref has property name same as last variable then return ref[last] else return Not Found
const obj = { first: { second: { third: 'done'} },hello: { world: { foo: { bar: 'wrong' } } },second: { third: 'wrong'}};
const arr = [ 'first', 'second', 'third' ];
function traverse(obj, arr) {
let ref = obj
let last = arr[arr.length-1]
for(let i=0; i<arr.length-1; i++){
let key = arr[i]
if(typeof ref[key] === 'object'){
ref = ref[key]
} else{
return "Not found"
}
}
return ref.hasOwnProperty(last) ? ref[last] : "Not found"
}
console.log(traverse(obj,arr))
console.log(traverse(obj,['first','third']))
console.log(traverse(obj,['hello','world']))
if you want it short and to handle system file path like :
it returns the object if exist and -1 if not
const obj = {
first: { second: { third: 'done'} },
hello: { world: { foo: { bar: 'wrong' } } },
second: { third: 'wrong'}
};
function traverse(obj, path) {
let seq = path.split('/').filter(x => x != '');
seq.map( s => obj = !obj[s] ? -1 : obj[s] );
return obj;
}
console.log(traverse(obj, '/hello/notexist/'));
console.log(traverse(obj, '/hello/world/'));
or even shorter with a reduce() instead of a map() :
function traverse(obj, path) {
let seq = path.split('/').filter(x => x != '');
return seq.reduce((acc, curr) => !acc[curr] ? -1 : acc[curr], obj);
}
or a even even shorter :
function traverse(obj, path) {
return path
.split('/')
.filter(Boolean)
.reduce((acc, curr) => acc[curr] ? acc[curr] : -1, obj);
}
const obj = {
first: { second: { third: 'done'} },
hello: { world: { foo: { bar: 'wrong' } } },
second: { third: 'wrong'}
};
const res = traverse(obj, '/hello/world/'); // return {foo:...}
console.log(res);

NGRX/REDUX: update value in deep object by json path

I have an object like:
{
categories: {
Professional: {
active: false,
names: [
{
id: 1,
name: "Golf",
active: false
},
{
id: 2,
name: "Ultimate Frisbee",
active: false
}
]
}}
and i want update categories.Professional.active with true, into the reducer i have:
return {
...state,
categories: {
...state.categories,
Professional: {
...state.categories.Professional,
active: true
}
}
}
now i want write a function for spreadfy an object and update a single property by json path. Eg.
return deepPatch(state, 'categories.Professional.active', true);
the goal for the function deepPatch is build at runtime this structure:
return Object.assign({}, obj, {
categories: Object.assign({}, state.categories, {
Professional: Object.assign({}, state.Professional, {
active: true
})
})
});
i have tried but don't know how make a recursive spread:
function deepPatch(obj: any, path: string; value: any){
const arrayPath: string[] = path.split('.');
const currObj = null;
for (let i = 0, e = arrayPath.length; i < e; i++) {
const currPath = arrayPath[i];
currObj = obj[currPath];
currObj = Object.assign({}, currObj, ???);
}
return currObj;
}
You could get the first key and create a new object by calling the function again until no more keys are available.
function deepPatch(object, path, value) {
var [key, rest] = path.match(/^[^.]+|[^.].*$/g);
return { ...object, [key]: rest
? deepPatch(object[key], rest, value)
: value
};
}
var state = { categories: { Professional: { active: false, names: [{ id: 1, name: "Golf", active: false }, { id: 2, name: "Ultimate Frisbee", active: false }] } } },
result = deepPatch(state, 'categories.Professional.active', true);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const deepSet = function (object, path, value) {
if (typeof path === 'string') {
path = path.split('.');
}
if (path.length > 1) {
const e = path.shift();
deepSet(object[e] = Object.prototype.toString.call(object[e]) === '[object Object]' ? object[e] : {}, path, value);
} else {
object[path[0]] = value;
}
};
I'm using this function. Works for me.

Why my 2 object deep comparison is failing?

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

How to change value of object attribute on some condition

I have an object like this.
Obj : {
USA : true
NZ : false,
Canada : true,
Japan : false,
China : true
Ind : false,
}
In my function I am getting countery.name = IND so on this condition how can I change the flag of respective country.
What I am trying on this here is
var countryName = countery.name // Getting some value here.
Object.keys(obj).map(function(i) {
/*if(countryName == obj.countryName){ // something missing here
obj.countryName.value
}*/
});
change to true or false. Opsite to current value
The solution using Object.keys function:
var Obj = {USA : true,NZ : false,Canada : true,Japan : false,China : true,Ind : false},
keys = Object.keys(Obj),
len = keys.length,
countryName = 'IND';
while (len--) {
if (keys[len].toLowerCase() == countryName.toLowerCase()) {
Obj[keys[len]] = !Obj[keys[len]];
break; // avoiding redundant iterations
}
}
console.log(Obj);
Get the properties of the object via Object.keys() and then use some() to iterate over properties toLowerCase() function to compare property and the country name. In if statement use return true to break the loop if the key is found.
var obj = {
USA: true,
NZ: false,
Canada: true,
Japan: false,
China: true,
Ind: false
};
var country = {
name: 'IND'
};
Object.keys(obj).some(key => {
if(key.toLowerCase() === country.name.toLowerCase()){
obj[key] = !obj[key];
return true;
}
});
console.log(obj);
You could use Array#some and exit the loop if found.
var obj = { USA: true, NZ: false, Canada: true, Japan: false, China: true, Ind: false },
countryName = 'IND';
Object.keys(obj).some(function (k) {
if (k.toLowerCase() === countryName.toLowerCase()) {
obj[k] = !obj[k];
return true;
}
});
console.log(obj);
You can change the object's property like obj[countryName] = !obj[countryName].value when the condition passes.
obj = {USA:true, NZ: false, Canada:true,Japan:false,China:true,Ind:false};
var countryName = 'Ind';
console.log("Before changing the flag:");
console.log(obj);
Object.keys(obj).map(function(i){
if (countryName == i)
obj[countryName] = !obj[countryName].value;
});
console.log("After changing the flag:");
console.log(obj);

Categories

Resources