Make the function immutable - javascript

I build following method that change the structure of the input objects. It works but I would like to make it immutable.
function formatObj(obj: Object) {
const keys = Object.keys(obj)
keys.forEach((key) => {
const value = obj[key]
if (value === null || value === undefined) {
return
}
else if (key === 'size') {
obj[key] = { value, min: 0, max: 0 }
}
else if (typeof value === 'object') {
formatObj(obj[key])
}
})
}
I tried this but it returns a list of undefined...
function formatObj(obj: Object) {
const keys = Object.keys(obj)
const r = keys.map((key) => {
const value = obj[key]
if (value === null || value === undefined) {
return { key: value }
}
else if (key === 'size') {
return { key: { value, min: 0, max: 0 } }
}
else if (typeof value === 'object') {
return formatObj(obj[key])
}
})
return r
}
Why? How can I solve?

The idiom you're looking for is Object.fromEntries(Object.entries(...).map...):
let mapObject = (obj, fn) => Object.fromEntries(
Object.entries(obj).map(([k, v]) => fn(k, v))
);
//
let test = {
a: 0,
size: 123,
sub: {
b: null,
size: 456
}
}
let transformer = (key, value) => {
if(key === 'size')
return [key, { value, min: 0, max: 0 }];
if(!value || typeof value !== 'object')
return [key, value];
return [key, mapObject(value, transformer)]
}
let test2 = mapObject(test, transformer)
console.log(test2)
One drawback of this is that you have to have an external transformer variable to be able to reapply it. A nicer approach would be to define the transformer inside the mapper and pass it around as an argument. Also, it won't hurt to make it a bit more generic:
function transform(obj, fn) {
function transformer(x) {
if (!x || typeof x !== 'object')
return x;
if (Array.isArray(x))
return x
.map((v, k) => fn(k, v, transformer))
.filter(x => x !== void 0)
.map(x => x[1]);
return Object.fromEntries(
Object.entries(x)
.map(([k, v]) => fn(k, v, transformer))
.filter(x => x !== void 0));
}
return transformer(obj);
}
//
let test = {
a: 0,
size: 123,
removeMe: 123,
renameMe: 456,
sub: {
b: null,
size: 456,
removeMe: 1230,
renameMe: 4560,
sub2: {
foo: ['bar', 'baz', {size: 798}]
}
}
}
let test2 = transform(test, (key, value, transformer) => {
// return undefined to remove a key
if (key === 'removeMe')
return;
// return a new key
if (key === 'renameMe')
return ['renamed!', value];
// return a new value
if (key === 'size')
return [key, {value, min: 0, max: 0}];
// keep transforming
return [key, transformer(value)]
});
document.write('<pre>' + JSON.stringify(test2,0,4))
Object.fromEntries is relatively new, but can be trivially polyfilled.

result of a Array map function is a list. In this case you probably want to use reduce function to make result object again. It would help better to understand if you provided some sample input and output data.
function formatObj(obj: Object) {
const keys = Object.keys(obj)
const r = keys.reduce((resultObj, key) => {
const value = obj[key]
if (value === null || value === undefined) {
return { ...resultObj, [key]: value }
}
else if (key === 'size') {
return { ...resultObj, [key]: { value, min: 0, max: 0 } }
}
else if (typeof value === 'object') {
return { ...resultObj, [key]: formatObj(obj[key]) }
}
}, {})
return r
}

Related

Extract the assignment of from this expression with sonarQube

when I launch a sonar control I find myself with a smells code for the following function:
const changeMedia = (value: any, step: any) => {
step.definition?.steps
.filter((obj: any) => obj.media.includes(value))
.map((item: any) => {
if (value === 'EMAIL') {
return (item.media = 'LETTER');
} else if (value === 'LETTER') {
return (item.media = 'EMAIL');
}
return item;
});
};
I get the following sonar alert:
Extract the assignment of "item.media" from this expression.
What would be the solution to avoid this sonar message?
const changeMedia = (value, step) => {
step.definition?.steps
.filter((obj) => obj.media.includes(value))
.map((item) => {
if (value === 'EMAIL') {
item.media = 'LETTER'
return item; // returning item instead of (item.media = 'LETTER')
} else if (value === 'LETTER') {
item.media = 'EMAIL'
return item;
}
return item;
});
};

Run a function recursivelly in Javascript

I created this function:
const demoD = (
obj,
toChange,
newV,
) => {
return Object.keys(obj).reduce((acc, key) => {
if (typeof obj[key] === 'object') {
demoD(obj[key], toChange, newV);
}
acc[key] = obj[key] === toChange ? newV : obj[key]
return acc
}, {})
}
const obj = {
name: '',
age: '687',
demo: {
test: ''
}
}
console.log(demoD(obj, '', null))
The idea of this function is to change a value from the object with a new one, and it should run recursivelly. I expect this result:
const obj = {
name: null',
age: '687',
demo: {
test: null'
}
}
But i don't get this result, and the test still unchanged. Question: How to fix and what is the issue?
If you want to apply change to the original object in place:
const demoD = (
obj,
toChange,
newV,
) => {
return Object.keys(obj).reduce((acc, key) => {
if (typeof obj[key] === 'object') {
demoD(obj[key], toChange, newV);
}
acc[key] = obj[key] === toChange ? newV : obj[key]
return acc
}, obj) // 👈 send in obj here
}
If you don’t want to mutate your original object, then it should be:
const demoD = (
obj,
toChange,
newV,
) => {
return Object.keys(obj).reduce((acc, key) => {
if (typeof obj[key] === 'object') {
acc[key] = demoD(obj[key], toChange, newV)
} else {
acc[key] = obj[key] === toChange ? newV : obj[key]
}
return acc
}, {})
}

search array within a object in javascript / typescript

I want to find the first array in an object in Javascript. However my functions cause an error:
el is undefined
el contains the object that looks like this:
data = {
foo: {
bar: [{object a},
{object b},
{object c}
]
}
}
let el = data;
el = this.searchArray(el);
el.forEach(element => {
console.log(element);
let siblingData = this.injectFilterParameters(element);
});
//here is unimportant code
searchArray(obj) {
if (Array.isArray(obj)) {
return obj;
} else {
Object.keys(obj).forEach(key => {
if (Array.isArray(obj[key])) {
return obj[key];
} else {
return this.searchArray(obj[key]);
}
})
}
}
const data = {
foo: {
bar: [
{ object: 'a' },
{ object: 'b' },
{ object: 'c' }
]
}
}
console.log(findArray(data))
function findArray(data: unknown): unknown[] | undefined {
if (Array.isArray(data)) {
return data
}
if (typeof data !== 'object') {
return undefined
}
const object = data as Record<string, unknown>
for (const key of Object.keys(object)) {
const value = object[key]
const valueSearchResult = findArray(value)
if (valueSearchResult != undefined) {
return valueSearchResult
}
}
return undefined
}

Check for non null array during filtering an object

Trying to filter the object to return only non null values.
Below is excerpt from my code. How do I check for non null values in the array job in this case?
const name = null,
age = '25',
job = [null];
const obj = {
name,
age,
job
};
const result = Object.fromEntries(
Object.entries(obj).filter(([_, value]) => value)
);
console.log(result)
Could anyone please help?
I was expecting the result to be
{
"age": "25"
}
First map the array in the entries to keep only truthy values, then filter the entries by whether the entry is truthy and not a 0-length array:
const name = null,
age = '25',
job = [null];
const obj = {
name,
age,
job
};
const result = Object.fromEntries(
Object.entries(obj)
.map(
([key, value]) => [key, Array.isArray(value) ? value.filter(v => v) : value]
)
.filter(([, value]) => value && (!Array.isArray(value) || value.length))
);
console.log(result)
If your arrays can only have one element, you can check if the value[0] is truthy as well.
const name = null,
age = '25',
job = [null];
const obj = {
name,
age,
job
};
const result = Object.fromEntries(
Object.entries(obj).filter(([_, value]) => value && value[0])
);
console.log(result)
This can be done as follows:
const name = null,
age = '25',
job = [null, 29];
const obj = {
name,
age,
job
};
let result = {
}
for (prop in obj) {
if (obj[prop]) {
if (Array.isArray(obj[prop])) {
result[prop] = obj[prop].filter(function (element) {
return element != null;
})
}
else {
result[prop] = obj[prop];
}
}
}
console.log(result)

How to check if an object property is undefined without a for in loop?

Say I have a function,
const doSomething = (a : String, b : Object) => {
const something = 'something';
if (!a) {
throw new Error(
'a is missing'
);
}
for (let prop in b) {
if (b[prop] === undefined) { // erroneously previously if (!b[prop])
throw new Error(
`prop ${prop} in b is missing`
);
}
}
return something;
}
Is there any way to avoid the for..in loop, and somehow check for an undefined property in b without it?
Something like,
const doSomething = (a : String, b : Object) => {
const something = 'something';
if (!a || Object.values(b.includes(undefined))) {
throw new Error(
`${!a ? 'a is missing' : 'b contains undefined'}`
);
}
return something;
}
But ideally I'd like something a little cleaner and to know the actual undefined prop.
You can use Object.entries and Array.some to determine if any property is undefined
function hasUndefinedValue(obj){
return Object.entries(obj).some(e => typeof e[1] === 'undefined');
}
Example below has a few test cases.
function hasUndefinedValue(obj){
return Object.entries(obj).some(e => typeof e[1] === 'undefined');
}
var tests = [
{a:"foo"},
{a:"foo", b: undefined},
{a:"foo",b:[1,2,3]}
];
for(var i = 0;i<tests.length;i++){
console.log(tests[i],hasUndefinedValue(tests[i]));
}
You can use Object.keys().
const obj = {
a: 'val',
b: undefined
};
Object.keys(obj)
.forEach(key => {
if(!obj[key]) {
throw new Error(`prop ${key} has undefined value`)
}
});
--Edit--
const obj = {
a: 'val',
b: false
};
Object.keys(obj)
.forEach(key => {
if(!obj[key]) {
throw new Error(`prop ${key} has undefined value`)
}
});
If only undefined case needs to be handled, then you achieve it by using obj[key] == undefined.

Categories