How to include data to object if element is not falsy? - javascript

I have the map:
map(([reportProperties, reportObjectsProperties, textProperties, visibleText]) => {
return { reportObjectsProperties, reportProperties, textProperties, visibleText };
}),
I try to check if all parameters are not falsy include them to result object like this:
map(([reportProperties, reportObjectsProperties, textProperties, visibleText]) => {
if(visibleText)
return { reportObjectsProperties, reportProperties, textProperties, visibleText };
if(reportObjectsProperties && reportObjectsProperties.length)
return { reportObjectsProperties, reportProperties, textProperties, visibleText };
....
}),
How to make this more elegant?

My suggestion is to look into Array.prototype.filter() method paired with
Object.entries to filter out the falsy values. And pair it with a reduce function to reconstruct the new object
I would take this into the following direction:
const arr = [{a: true, b: true, c: false}, {a: false, b: true}]
const excludeKeys = ["c"]
let result = arr.map(obj => {
return Object.entries(obj).filter(([key, value]) => !excludeKeys.includes(key) && value)
.reduce((prev, [key, value]) => ({...prev, [key]: value}), {})
})
This provides a functional and pretty generic interface for filtering out the falsy / excluded object key / values.

Assuming your object is an array, you can check if all values exists using every method. And you don't need map there because you don't do a transformation on your object.
instead of
.map(arr => {
... return arr;
});
do
.filter(row => row.every(cell => typeof cell !== undefined && cell !== null));

Related

Deep map/transform js object with ability to alter keys and values

I've seen lots of solutions for "deep mapping" over an object to change either the keys of the object, or the value of the object, but not both.
For example if given the following object:
const obj = {
this: {
is_not: {
something: "we want to keep"
},
is: {
a: {
good_idea: 22
},
b: {
bad_idea: 67
},
c: [{
meh_idea: 22
}]
}
}
}
Essentially I'd like to be able to transform it with the following function signature:
const newObj = deepTransform(obj, iterator)
function iterator (key, value) {
if (key.endsWith("idea")) return { [key.replace("idea", "")]: value + 1}
else if (key === "is_not") return {}
else return {[key]: value}
}
Running deepTransform would result in an object that looks like:
{
this: {
is: {
a: {
good: 23
},
b: {
bad: 68
},
c: [{
meh: 23
}]
}
}
}
If anyone could help me create this function that would be awesome! I'd be more than happy if it used lodash functions under the hood.
Please ask if anything is unclear. Thanks!
You can use lodash's _.transform() to recursively iterate objects/arrays, and rebuild them with updated keys and values:
const { transform, toPairs, isObject } = _
const deepTransform = (obj, iterator) => transform(obj, (acc, val, key) => {
const pair = toPairs(iterator(key, val))[0] // use the iterator and get a pair of key and value
if(!pair) return // if pair is undefined, continue to next iteration
const [k, v] = pair // get the update key and value from the pair
// set the updated key and value, and if the value is an object iterate it as well
acc[k] = isObject(v) ? deepTransform(v, iterator) : v
})
const obj = {"this":{"is_not":{"something":"we want to keep"},"is":{"a":{"good_idea":22},"b":{"bad_idea":67},"c":[{"meh_idea":22}]}}}
const iterator = (key, value) => {
if (String(key).endsWith("_idea")) return { [key.replace("_idea", "")]: value + 1}
if (key === "is_not") return {}
return {[key]: value}
}
const newObj = deepTransform(obj, iterator)
console.log(newObj)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>
Changing the iterator a to return a pair of [key, value], would make _.toPairs() redundant, and would simplify the code:
const { transform, isObject, isUndefined } = _
const deepTransform = (obj, iterator) => transform(obj, (acc, val, key) => {
const [k, v] = iterator(key, val) // use the iterator and get a pair of key and value
if(isUndefined(k)) return // skip if no updated key
// set the updated key and value, and if the value is an object iterate it as well
acc[k] = isObject(v) ? deepTransform(v, iterator) : v
})
const obj = {"this":{"is_not":{"something":"we want to keep"},"is":{"a":{"good_idea":22},"b":{"bad_idea":67},"c":[{"meh_idea":22}]}}}
const iterator = (key, value) => {
if (String(key).endsWith("_idea")) return [key.replace("_idea", ""), value + 1]
if (key === "is_not") return []
return [key, value]
}
const newObj = deepTransform(obj, iterator)
console.log(newObj)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>

Adding elements in the array on the basis of an object

const prefer={"cs":{"c1":true,"c2":true,"c3":false,"c4":true}}
setArray((prevArray)=>{
return prevArray.filter(function(array){
//code here
})
});
I want to set array on the basis of prefer object, like if c1 is true then is want to add it in the array
Do I need to initialize the array too?
In above example array=["c1","c2","c4"] .
Also do we need filter method or can we use some other method
You can use for-in loop to iterate map/object
const prefer = { c1: true, c2: true, c3: false, c4: true };
let arr = [];
for (let key in prefer) {
if (prefer[key]) arr.push(key); // modify how u want to save
// arr.push({ [key]: prefer[key] }); // if u want key and value
}
console.log(arr);
You can express it as follows,
const prefer = { "c1":true, "c2":true, "c3":false, "c4":true }
const output = Object.entries(prefer)
.filter(([, value]) => value) // !!value or Boolean(value) to coerce
.map(([key]) => key)
console.log(output)
This is sort of convoluted, but it works:
const prefer = {
"c1": true,
"c2": true,
"c3": false,
"c4": true
}
var setArray = ( prevArray ) => {
return Object.keys(prevArray).filter(function(val, i) {
if ( prevArray[Object.keys(prevArray)[i]] ) return Object.keys(prevArray)[i];
})
};
console.log( setArray(prefer) );

How to use a reduce to filter fields in object

I need to write a reduce function that takes an object and returns a new one but only with string-type fields.
E.g.
Input: { type: 'pilot', isActive: true }
Output: { type: 'pilot' }
Input: { isActive: true }
Output: {}
At the first step you need to verify the type of values of input object, and if they are not 'string' => put to the new Object. Also use "Object.keys" to apply reduce to object.
It can be look like:
const newObj = return Object.keys(obj).reduce(
(acc, rec) => {
if (typeof obj[rec] === 'string') {
return { ...acc, [rec]: obj[rec] }
}
return acc
},
{}
)
You need only put this code to your function.
If not particular about reduce, using Object.entries and Object.fromEntries will simplify.
const obj1 = { type: "pilot", isActive: true };
const obj2 = { isActive: true };
const filter = obj =>
Object.fromEntries(
Object.entries(obj).filter(([, value]) => typeof value === "string")
);
console.log(filter(obj1));
console.log(filter(obj2));

Parsing URL parameters and return javascript object with an array inside

I have the following url with params:
abc?carbs=pasta,rice,noodles&sauce=tomato&wine=red
As you can see, carbs is an array divided by a comma.
So, by using the following code:
sourceURL
.slice(1)
.split('&')
.map(p => p.split('='))
.reduce((obj, pair) => {
const [key, value] = pair.map(decodeURIComponent);
return { ...obj, [key]: value };
}, {});
I get this NOT CORRECT result:
{
"carbs": "pasta,rice,noodles",
"sauce": "tomato",
"wine": "red"
}
What I would like is the following: (EXPECTED)
{
"carbs": ["pasta","rice","noodles"],
"sauce": "tomato",
"wine": "red"
}
Any way someone can help? Thanks in advance, Joe
UPDATE:
Some of the responses are great, thanks, everyone. Unfortunately, they all return carbs as an Object if it contains only 1 value.
For example, if my URL is abc?carbs=noodles&sauce=tomato&wine=red I should get:
{
"carbs": ["noodles"], <----- still an array even with 1
"sauce": "tomato",
"wine": "red"
}
but unfortunately, all the provided solutions return the following:
{
"carbs": "noodles",
"sauce": "tomato",
"wine": "red"
}
Sorry if it wasn't clear before. Thanks Joe
Use URLSearchParams to parse the string and .reduce to get the desired result :
EDIT
Carbs needs always to be an array, even if it's empty
add a check for the key inside .reduce :
const sourceURL = "abc?carbs=&sauce=tomato&wine=red";
const parsed = new URLSearchParams(sourceURL.split("?")[1]);
const result = [...parsed.entries()].reduce((acc, [key, value]) => {
if (key === "carbs") acc[key] = value.split(",").filter(e => e);
else acc[key] = value.includes(",") ? value.split(",") : value;
return acc;
}, {});
console.log(result);
Maybe something like this?
const sourceURL = "abc?carbs=pasta,rice,noodles&sauce=tomato&wine=red"
const res = sourceURL
.split("?")
.pop()
.split('&')
.map(p => p.split('='))
.reduce((obj, pair) => {
const [key, value] = pair.map(decodeURIComponent);
return { ...obj, [key]: value.indexOf(",") >= 0 ? value.split(",") : value };
}, {});
console.log(res)
OUTPUT
{ carbs: [ 'pasta', 'rice', 'noodles' ],
sauce: 'tomato',
wine: 'red' }
You can use a RegExp to get the pairs, and then split the pairs by equal and comma signs, normalise the subarrays to have an entry structure [key, value], and then convert to and Object with Object.fromEntries():
const parse = url => Object.fromEntries( // convert the entries to an object
url.match(/[^?=&]+=[^?=&]+/g) // match all pairs with = in the middle
.map(s => s.split(/[=,]/)) // split the pairs by = and ,
.map(arr => arr.length > 2 ? [arr[0], arr.slice(1)] : arr) // normalise the entries
)
console.log(parse('abc?carbs=pasta,rice,noodles&sauce=tomato&wine=red'))
console.log(parse('abc?carbs=pasta&sauce=tomato&wine=red'))
Could you not check for commas and if found do a split ?
.slice(1)
.split('&')
.map(p => p.split('='))
.reduce((obj, pair) => {
let [key, value] = pair.map(decodeURIComponent);
value = value.indexOf(',') !== -1 ? value.split(',') : value;
return { ...obj, [key]: value };
}, {});

Filter object of object with RxJS

I have the following stream and I would like to filter and keep only the params that are true(boolean).
{
"colors": {
"red": "",
"yellow": true,
"green": false
},
"size": {
"t5": true,
"t10": "",
"t20": true
}
}
I need the output to look like this:
{
"colors": ["yellow"],
"size": ["t5,t20"]
}
Here are my thougt when I try to deal with this:
I tried to use map but without success. I guess it's because it's an
object and not an array.
All keys and values are dynamic in this object so I can't use them to
manipulate the object.
flatMap() help me a bit but I don't know what to do with it as I
don't know the name of the keys.
this.form.valueChanges
.debounceTime(400)
.flatMap(x => Object.values(x))
.subscribe(console.log)
This is not an rxjs issue, just a plain js mapping:
getTruthyKeys(obj: any): Array<string> {
return Object.keys(obj).filter(key => obj[key] === true);
}
mainKeysToTruthyLists(obj: any): Array<{key: string, truthy: Array<string>}> {
let result = {};
Object.keys(obj).forEach(key => result[key] = getTruthyKeys(obj[key]);
return result;
}
and now you can apply it as a map on your stream:
this.form.valueChanges
.debounceTime(400)
.map(mainKeysToTruthyLists)
.subscribe(console.log)
This isn't really a RxJS question. All that RxJS should do here is map. This can be done with Object.entries:
this.form.valueChanges
.debounceTime(400)
.map(obj => {
return Object.entries(obj)
.reduce((newObj, [key, subObj]) => {
const subArr = Object.entries(subObj)
.filter(([, subValue]) => subValue)
.map(([subKey]) => subKey);
return Object.assign(newObj, { [key]: subArr });
}, {})
})

Categories

Resources