Can the props in a destructuring assignment be transformed in place? - javascript

This works…
const { prop1:val1, prop2:val2 ) = req.query
val1 = val1.toLowerCase()
Though, I'm more inclined to do something like
const { prop1.toLowerCase():val1, prop2:val2 } = req.query
or
const { prop1:val1.toLowerCase(), prop2:val2 } = req.query
neither of which work. Is there a syntax similar to this or must manipulations be done outside of the destructing assignment?

No, this is not possible. A destructuring assignment does only assign, it does not do arbitrary transformations on the value. (Setters are an exception, but they would only complicate this).
I would recommend to write
const { prop1, prop2:val2 ) = req.query;
const val1 = prop1.toLowerCase();
or, in one statement:
const { prop1, prop2:val2 ) = req.query, val1 = prop1.toLowerCase();

The trouble with the temporary variable solutions is that they introduce different versions of the same data into the scope, which can lead to bugs.
This solution creates a utility function that receives the object to be destructured as well as a second object that is a mapping of property names to transformation functions. It's a little more verbose, but does the trick.
// Utility functions to perform specified transformations on an object
function transformProps(obj, trans) {
return Object.assign({}, obj, ...Object.entries(trans).map(([prop, fn]) =>
prop in obj ? {[prop]: fn(obj[prop])} : null
));
}
const { prop1:val1, prop2:val2 } = transformProps(
{prop1: "FOO", prop2: "BAR"},
{prop1: v => v.toLowerCase()} // Transformations to be made
);
console.log(val1, val2);

Related

Merge function return into bbject: Spread vs Object.assign [duplicate]

I'm reading an introduction to Redux reducers (https://redux.js.org/introduction/three-principles) which contains the following example of a reducer:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
It seems from its documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) that Object.assign() will 'merge together' all the objects passed into it. In this case, however, todo and {completed: true} are already objects, so I don't see the point of passing an empty object literal, {}, as the first argument to Object.assign(). Can anybody clarify this?
When you use Object.assign, the first object you give it will have all the rest of the objects merged into it. That is to say, the first object will be mutated.
If you want to avoid mutating the objects you're merging, it's helpful to pass in the empty object as the first parameter to prevent any of the component objects from changing.
Here's an example demonstrating the difference:
const obj1 = {
foo: "bar"
}
const obj2 = {
key: "value"
}
// Here, obj1 is the same after the Object.assign call
console.log(Object.assign({}, obj1, obj2));
console.log(obj1)
console.log(obj2)
console.log("\n\n")
// Note that after this call, obj1 holds both keys. So this will mutate it:
console.log(Object.assign(obj1, obj2));
console.log(obj1) // This is different now
console.log(obj2)
If you don't pass an empty object in, the original todo object will be modified. This may be what you want, but more often than not it isn't.
This is due to the way objects are all references, and are not cloned by default.
Short answer: Objects and Arrays are assignment by reference.
In this example, changing one will change the other, they are not immutable:
let x = {param:1}
const foo = (a) => {
a.param +=1;
console.log('response', x, a)
}
foo(x);
To fix that, we use Object.assign()
let x = {param:1}
const foo = (a) => {
let b = Object.assign({}, a);
b.param +=1;
console.log('response', b, x)
}
foo(x);

Can you reference a destructured function parameter in javascript?

I'm going to begin by saying that this is purely a matter of syntax candy-
I have a number of javascript functions with this general signature:
const someFn = async (context, args) => {}
Each implementation deconstructs those two objects. For example:
const myHttpFn = async({req, res}, {id, name, potato}) => { ... }
Within the implementation, I'd like to be able to deconstruct the object in the signature (this makes it easy for users to see what arguments are truly required) but still have a reference to the rest of the object (some properties are pass-through). The closest I've come has been by wrapping both arguments in an object like so:
const { isCold, isDry } = require('./some/utilities');
const lunch({context, context:{fridge, pantry, sandwitchMaker}, {ingredientList, shouldHeat}) => {
const coldList = ingredientList.filter(isCold);
const dryList = ingredientList.filter(isDry);
const [cold, dry] = await Promise.all(
fridge.fetch(context, { list: coldList }),
pantry.fetch(context, { list: dryList })
);
return sandwitchMaker.cook(context, { cold, dry, shouldHeat });
}
const sandwitch = await lunch({context, {
ingredientList: ["bread", "cheese", "tomato", "bacon", "bacon", "lettuce", "mayo"],
shouldHeat: true
}});
Adding the wrapper object gives me access to both context as a whole as well as the pieces that I've deconstructed. Is there a way to do this without a wrapper object?
You can't do it without your so-called wrapper object, but you can have just the wrapper object in the function definition - something like
const lunch({context: {fridge, pantry, sandwitchMaker, ...other}, {ingredientList, shouldHeat}) => {
const context = {fridge, pantry, sandwitchMaker, ...other};
//... rest of your code
}

Is destructuring this possible?

I am new to destructuring and need help finding the best solution. I have a pretty complicated object coming back as a response and would like to clean it up. Without doing anything, it looks like this:
const homeTeam = {
totalPlaysFor: res.data.stats.home.teamStats[0].miscellaneous.offensePlays,
totalPlaysAgainst: res.data.stats.away.teamStats[0].miscellaneous.offensePlays
}
I know I can do something like:
const { offensePlays } = res.data.stats.home.teamStats[0].miscellaneous;
but that only solves my problem for one of them and would leave the other still long and tough to read.
You can destructure the stats property in advance, then make a helper function that takes the .home or .away object and navigates to the nested .offensePlays:
const { stats } = res.data;
const getOffPlays = obj => obj.teamStats[0].miscellaneous.offensePlays;
const homeTeam = {
totalPlaysFor: getOffPlays(stats.home),
totalPlaysAgainst: getOffPlays(stats.away)
};
Without having a standalone helper function, you could also create the object by .mapping an array of properties (eg [['totalPlaysFor', 'home'], ['totalPlaysAgainst', 'away']]) and pass it to Object.fromEntries, but that would be significantly less readable IMO.
While you can destructure this directly, via something like
const {
data: {
stats: {
home: {
teamStats: [{
miscellaneous: {
offensePlays: totalPlaysFor
}
}]
},
away: {
teamStats: [{
miscellaneous: {
offensePlays: totalPlaysAgainst
}
}]
}
}
}
} = res
const homeTeam = {totalPlaysFor, totalPlaysAgainst}
that feels pretty ugly.
Regardless whether you think your code is ugly, I can see a much more important problem with it: it doesn't work when any of those properties doesn't exist. To solve that, you might want a feature not yet ubiquitous in the language, one of Optional Chaining that would avoid throwing errors for a nested path such as 'data?.stats?.home?.teamStats?.0?.miscellaneous?.offensePlays', simply returning undefined if one of the nodes mentioned does not exist.
An equivalent feature is available as a function in many libraries. Underscore's property, lodash's property, and Ramda's path offer slightly different versions of this. (Disclaimer: I'm a Ramda author.) However it's easy enough to write our own:
const getPath = (pathStr) => (obj) =>
pathStr .split ('.')
.reduce ((o, p) => (o || {}) [p], obj)
const res = {data: {stats: {home: {teamStats: [{miscellaneous: {offensePlays: "foo"}}]}, away: {teamStats: [{miscellaneous: {offensePlays: "bar"}}]}}}}
const homeTeam = {
totalPlaysFor: getPath ('data.stats.home.teamStats.0.miscellaneous.offensePlays') (res),
totalPlaysAgainst: getPath ('data.stats.away.teamStats.0.miscellaneous.offensePlays') (res)
}
console .log (homeTeam)
Note that the array is not delineated in this simple version as [0] but just as .0. Obviously we could make this more sophisticated if necessary.
You can destructure as much as you want!
const homeTeam = {
totalPlaysFor: res.data.stats.home.teamStats[0].miscellaneous.offensePlays,
totalPlaysAgainst: res.data.stats.away.teamStats[0].miscellaneous.offensePlays
}
You can even use a combination of array destructuring / object destructuring here:
const [ home ] = res.data.stats.home.teamStats
const [ away ] = res.data.stats.away.teamStats
const { offensePlays: totalPlaysFor } = home.miscellaneous
const { offensePlays: totalPlaysAgainst } = away.miscellaneous
const hometeam = { totalPlaysFor, totalPlaysAgainst }
Or, if you want a more reusable solution, you can use parameter destructuring:
const getTeam = (
{ miscellaneous: { offensePlays: totalPlaysFor } },
{ miscellaneous: { offensePlays: totalPlaysAgainst } }
) => ({
totalPlaysFor,
totalPlaysAgainst
})
Then you can use it like:
const [ home ] = res.data.stats.home.teamStats
const [ away ] = res.data.stats.away.teamStats
const homeTeam = getTeam(home, away)

How can i use ramda.js with this code?

I am beginning to use ramda, but have doubts about how to implement functions ramda.
This code is to mount a select object to do queries for sequelize.
See code:
const stringRequired = string => !!string.length
const numberRequired = number => Number.isInteger(number)
const validadeInput = (value, initial, validade) => (
validade(value) ? value : initial
)
const input = (obj, key, initial, validade) => (
typeof obj[key] !== 'undefined' ?
validadeInput(obj[key], initial, validade) :
initial
);
const addValue = (obj, key, value) => {
const prop = {}
prop[key] = value
return Object.assign(obj, prop)
}
const addFilter = (obj, key, value, validate) => (
validate(value) ? addValue(obj, key, value)
: obj
)
const selector = (query = {}) => {
const limit = input(query, 'limit', 10, numberRequired);
const name = input(query, 'name', '', stringRequired);
let select = {}
select = addFilter(select, 'name', name, stringRequired);
select = addFilter(select, 'limit', limit, numberRequired);
return select
}
console.log(selector());
// { limit: 10 }
console.log(selector({ name: 'David Costa' }));
// { limit: 10, name: 'David Costa' }
console.log(selector({ limit: 50 }));
// { limit: 50 }
Or see demo on link
http://jsbin.com/zerahay/edit?js,console,output
Why?
I think you need to consider why you want to convert this to Ramda
(Disclaimer: I'm a Ramda author) Ramda is a library, a toolkit. Use it when it helps clean up your code or when it makes it easier to understand the problem and its solution. Don't use it when it doesn't.
That said, I did refactor it, using Ramda:
A Refactoring
I simply tried to refactor it a bit. In the end, I replaced all your helper functions with a single one that takes a list of conditions such as ['limit', 10, numberRequired] to create a function equivalent to your selector.
I did use a few Ramda functions along the way, but the only one that offers substantive help is assoc, which creates a new object from an old one and a key and value. Using, for instance, compose(Boolean, length) is cleaner than const stringRequired = string => !!string.length, but it's not a large difference.
The important change, to my mind, is the makeSelector function, which makes creating your selector function much more declarative. It's a bit ugly, and I probably would write it differently if I were starting from scratch, but I did this in a series of steps, inlining your helper functions until I had a reasonably short function that had the same behavior.
// rules
const stringRequired = R.compose(Boolean, R.length)
const numberRequired = number => Number.isInteger(number)
// utils
const makeSelector = (conditions) => (query = {}) => R.reduce(
(select, [key, initial, validate]) => {
const value = key in select && validate(select[key]) ? select[key] : initial;
return validate(value) ? R.assoc(key, value, select) : select
},
query,
conditions
)
// main
const selector = makeSelector([
['limit', 10, numberRequired],
['name', '', stringRequired]
])
console.log(selector());
console.log(selector({ name: 'David Costa' }));
console.log(selector({ limit: 50 }));
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
I'm a little hesitant to simply respond with a translated block of code and my recommendation for learning going forward would be to try replace the functions you already have one by one with those from Ramda.
Hesitation aside, the following offers one example of what your code might look like when using various functions from Ramda.
// The existing check for `!!string.length` allows for potential issues with
// arrays being passed in and incorrectly validating, so this will ensure the
// value is indeed a string before checking its length
const stringRequired = R.both(R.is(String), R.complement(R.isEmpty))
// `addFilter` produces a function that takes an object and uses the provided
// function to validate the value associated with the provided key if it exists
// otherwise using the provided `initial` value. If valid, an object containing
// the key and value will be returned, otherwise an empty object is returned.
const addFilter = (validate, initial, key) =>
R.pipe(
R.propOr(initial, key),
R.ifElse(validate, R.objOf(key), R.always({}))
)
// `selector` takes an object and passes it to each function generated by
// calling `addFilter`, merging the resulting objects together.
const selector = (q = {}) =>
R.converge(R.merge, [
addFilter(stringRequired, '', 'name'),
addFilter(Number.isInteger, 10, 'limit')
])(q)
console.log(selector())
console.log(selector({ name: 'David Costa' }))
console.log(selector({ limit: 50 }))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

Parsing structured data in a functional way (e.g. without mutating)

Imagine you have the following data in a file:
Group1
Thing1
Thing2
Group2
Thing1
Thing2
Thing3
Group3
Group4
Thing1
It's easy to write a "parser" which loops through the file line-by-line, remembering the current Group (in a variable) and then writing all the Things to an object, neatly grouped by their respective group:
// Very naive implementation for illustrative purposes only
let groups = {}
let currentGroup = null
data
.split(/\n/)
.forEach(entry => {
const matches = entry.match(/^(Group\d+)$/)
if (matches) {
currentGroup = matches[1]
groups[currentGroup] = []
} else {
groups[currentGroup].push(entry.trim())
}
})
which gives me:
{
Group1: [
'Thing1', 'Thing2'
],
Group2: [
'Thing1', 'Thing2', 'Thing3'
],
...
}
What's the best way to achieve this without mutating groups and currentGroup, in a purely functional way? Do I need to take a harder look at Array.reduce, because I've seen some (IMHO rather mind-boggling) use-cases to transform an Array into an Object, or is that not going to help here?
Yes, you'd want to use reduce here:
data
.split(/\n/)
.reduce(({groups, currentGroup}, entry) => {
const matches = entry.match(/^(Group\d+)$/)
if (matches) {
groups[matches[1]] = []
return {currentGroup: matches[1], groups};
} else {
groups[currentGroup] = groups[currentGroup].concat([entry.trim()]);
return {currentGroup, groups};
}
}, {groups: {}, currentGroup: null})
.groups
However, there is no reasonable way in JS to create a map object without mutation. As long as you keep your property assignments local, there's nothing wrong with that.

Categories

Resources