Functional programming / Ramda: Creating a new object by picking nested properties - javascript

Functional programming newbie here. I have this object:
{
_id: '2014d5db-55dc-4078-ae87-382c226d0785',
_source: {
phone: '00447827434313',
...
}
}
In the end I want to have it in this format:
{
id: '2014d5db-55dc-4078-ae87-382c226d0785',
phone: '00447827434313',
...
}
Basically extracting _source, and renaming _id to id.
I created this function below which works, but I'm trying use only Ramda's functions instead of creating new objects by hand. I assume it's more "functional" way, let me know if it doesn't really matter.
const test = o => merge(o._source, { id: o._id })
Thanks very much

I don't think there's a particular built-in Ramda function for that. But it's not hard to write one on top of lensPath, view, and map:
const remap = R.curry((desc, obj) => R.map(path => R.view(R.lensPath(path), obj), desc));
const myExtract = remap({
id: ['_id'],
phone: ['_source', 'phone']
});
myExtract(input);
//=> {"id": "2014d5db-55dc-4078-ae87-382c226d0785", "phone": "00447827434313"}
It only works this simply if your output is described as a flat list of fields (of course their properties could themselves be objects.) But one where you pulled from nested paths and pushed to nested paths would not be too much harder to write. The user API would be uglier, though, I imagine.
I don't see any clean way to make this points-free and still retain readability. Perhaps someone else might manage that, but I think this is already pretty nice.
You can see this in action on the Ramda REPL.

Related

how can I destructure from an array of objects when data is coming from database dynamically?

// foodItems is an array of object where one of it's key is id.
const [id1, id2, id3] = foodItems.map((item) => item.id);
// console.log(id1, id2, id3);
I can get id's by destructuring when array is static by doing above's code. But, How can I get the same id's when the data is coming from database dynamically in this array?
Edit: As Bergi pointed out: this horrible abomination does not even work if used in a different scope than root. But again, even if it had worked, don't do it like that.
Can you try to explain what you are really trying to do with that? Then we might be able to suggest a proper solution.
Old answer for historical reasons:
I don't really know you use-case for this but it generally seems like a bad idea. This is a poor pattern, do not use it, but it should answer your question anyways. So here is an example:
['a', 'b', 'c'].forEach((data, i) => {
this[`id${i+1}`] = data
})
Would lead to this:
id1: a
id2: b
id3: c
For your specific code, assuming the object has an "id" column that should be used (which would still prefix the variable with "id"):
foodItems.forEach((data) => {
this[`id${data.id}`] = data
})
Assuming an object like
const foodItems = [{id: 'foo', value: 'bar'}], it would result in this:
idfoo: {id: 'foo', value: 'bar'}
If you can provide some more context, maybe we could come up with a better solution that actually helps you?

Improving a React Ui Component

I need suggestions on improving a UI component that receives an object that contains all data types i.e. string, int, boolean, arrays and object and displays all the data types.
example of the object. PS: this object is also provide in the codesandbox link.
const data = {
foo1: {},
foo9: true,
foo3: "some random string",
foo4: "some random string",
created_at: {
$date: 1637368143.618
},
sources: {
foo1: {
first_date: {
$date: 1637368143.618
}
}
},
download: {
status: "pending"
},
foo8: "some random string",
foo5: "some random string",
foo7: ["sms"],
foo10: "some random string",
foo11: {
bar5: 0,
bar3: null,
bar1: null,
bar2: 0
},
foo28: ["some random string", "some random string2"],
foo19: "some random string"
};
currently i loop through the object and display the data in a card.
Object.entries(objSorted).map(([key, value])
but first the object is sorted to the info in the following order of string, int or boolean, array and object.
this is the link to the codesandbox. here
PS: This is more of a suggestion giving than a question, i don't have any problem with the code. i only want to know if the way am currently displaying this data is good and user friendly(i don't think so that's why i am asking). and if you think there is a better way you can comment or edit the codesandbox or create a new codesandbox.
Your intuition is correct! We can make this a little easier to understand by decomposing everything in a way similar to what you mentioned. What we really want is better separation of concerns. All of this hand waves details away, but the concepts will be what you take with you.
You have data.
Sort the data.
Loop through the outputs from step 2 and render the cards.
profit?
Step 1/2. You have data. Why not digest this before rendering? There are tons of ways of doing this, but for the sake of readability lets create a hook and be conscience about our downstream consumers. We can make our ObjectExtract component way simpler if it has well formatted data... And we can memo the results so that our digested data is cached and therefor doesn't recompute ever render.
const useCardData = (data) => {
return useMemo(() => {
const entries = Object.entries(data)
.map(([key, value]) => {
let type = "unknown";
if (Array.isArray(key)) {
type = "array";
} else if (typeof value === "boolean") {
type = "boolean";
} else if { ... };
return { type, value, key, priority: getTypeSortId(type) };
})
.sort((x, y) => x.priority - y.priority)
return entries;
}, [data]);
};
We digest our data as much as we can. The key is needed later because using index as a key is almost never what you want since when the order changes, react uses keys to track how rows should be shifted. Numeric indexes from arrays really don't describe the uniqueness of the record. Imagine sorting the records, index 1 could mean completely different things before and after sort, and your data may or may not show up correctly. We use the object property name as our unique key, since objects do a good job of not allowing duplicate keys.
Step 3. Let's use the hook and render the data.
const CardRenderer = ({ data }) => {
const cards = useCardData(data);
return (
<>
{cards.map((thing) => {
switch (thing.type) {
case "boolean":
return <BooleanCard key={thing.key} data={thing} />;
case "string":
return <StringCard key={thing.key} data={thing} />;
case "...":
return ...;
}
}}
</>
)
};
Since our data is well formed we can simply loop over it with a switch. Instead of defining all cases and their results in one component, notice how we just create smaller card components to handle each specific case. Separation of concern. The boolean card cares about what booleans look like ect. This makes it SUPER easy to test since you can test each card individually and then test the output of our CardRenderer to make sure its output is reasonable.
Step 4. profit. We broke everything down to components. Each piece only cares about a specific responsibility and that makes it easy to glue things together. It makes it composable. We can test each piece by itself and make sure its doing the right thing. We can make some really complex things like this while keeping the complexity of each individual hidden away.
What I described is SOLID. The examples in the link are in PHP but I think you'll get the gist. We can and should use the same patterns in react to build really cool things while managing the every growing complexity of cooler things.

How to transform object map to array with Ramda?

I'd like to transfrom the following object:
{
'id-1': { prop: 'val1' },
'id-2': { prop: 'val2' },
}
To array:
[
{ id: 'id-1', prop: 'val1' },
{ id: 'id-2', prop: 'val2' },
]
What I have done so far (it works):
R.pipe(
R.toPairs,
R.map(([id, props]) => ({
id,
...props,
}))
)
I'd like to solve it using Ramda only - if possible.
I'd suggest that solving it "using Ramda only" is a bad design goal, unless this is an exercise in learning Ramda. I'm one of the founders of Ramda and a big fan, but Ramda is only a toolkit meant to simplify your code, to make it easier to work in a certain paradigm.
That said, we could certainly write a point-free version of this using Ramda. The first thing that comes to my mind would be this*:
const transform = pipe(
toPairs,
map(apply(useWith(merge, [objOf('id'), identity])))
)
const data = {'id-1': { prop: 'val1' }, 'id-2': { prop: 'val2'}}
console.log(transform(data))
<script src="https://bundle.run/ramda#0.26.1"></script><script>
const {pipe, toPairs, map, apply, useWith, merge, objOf, identity} = ramda </script>
But this is less readable than your original, not more.
This code:
const transform = pipe(
toPairs,
map(([id, props]) => ({...props, id}))
)
is crystal-clear, whereas that Ramda version requires one to understand Ramda-specific useWith and objOf and slightly obscure apply -- I would hope that map, merge, and identity are clear.
In fact, this code is simple enough that I might well write it as a one-liner, in which case, I switch to compose over pipe:
const transform = compose(map(([id, props]) => ({...props, id})), toPairs)
But I probably wouldn't do so, as I find that multi-line pipe version easier to read.
Finally note that we can do this in a fairly readable way without any Ramda tools at all:
const transform = (data) =>
Object.entries(data).map(
([id, props]) => ({...props, id})
)
If I was already using Ramda in my code-base, I would prefer the pipe version above to this; I think it's somewhat easier to read. But would never introduce Ramda into a project only for that fairly minor difference.
I worry that people make a fetish over point-free code. It's a tool. Use it when it makes your code more understandable. Skip it when it makes your code more obscure. Here I think you're starting from quite readable code; it's difficult to improve on it.
*Note that identity here is not strictly necessary; you can skip it with no harm. The function generated by useWith without that identity will incorrectly report an arity of 1, but since the function is immediately wrapped with apply and then further placed in the context of receiving the a two-element array from toPairs, there is nothing which depends upon that arity. But I find it a good habit to include it regardless.
what about this?
probably less verbose!
const toArray = R.pipe(
R.toPairs,
R.map(
R.apply(R.assoc('id')),
),
);
const data = {
'id-1': { prop: 'val1' },
'id-2': { prop: 'val2' },
};
console.log('result', toArray(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Trying to return a deep nested object with Lodash

I have not been able to find a solution for this on StackoverlFlow and i have been working on this issue for some time now. Its a bit difficult so please let me know if i should provide more information.
My problem:
I have a JSON object of Lands, each land has an ID and a Blocks array associated with it and that blocks array has block with ID's too.
Preview:
var Lands = [{
'LandId':'123',
'something1':'qwerty',
'something2':'qwerty',
'Blocks': [
{
'id':'456',
'LandId':'123'
},
{
'BlockId':'789',
'LandId':'123'
}
]
},
...More Land Objects];
Note: The data is not setup the way i would have done it but this was done along time ago and i have to work with what i have for right now.
I am trying to write a lodash function that will take the blockId's that i have and match them and return the landId from the Blocks.
so the end result would be a list of LandId's that where returned from the Blocks.
I was using something like this and it was returning no results:
selectedLand = function(Lands, landIDs){
return _.filter(Lands, function(land){
return land === landIDs[index];
});
};
I know i am going about this the wrong way and would like to know the appropriate way to approach this and solve it. any help is much appreciated.
selectedLand = function(Lands, landIDs){
return _.filter(Lands, function(land){
return land === landIDs[index];
});
};
Note that index lacks any definition in this scope, so this function will essentially never return true, barring a lucky(?) accident with an externally defined index. And anytime you call _.filter(someArr,function(){return false;}) you'll get []. Undefined index aside, this does strict comparison of a land object against (maybe) a string in landIDs
I'm a bit unclear on the exact requirements of your selection, so you can tailor this filter to suit your needs. The function filters the lands array by checking if the .blocks array property has some values where the .landID property is included in the landsID array.
In closing, if you want to make the most out of lodash (or my favorite, ramda.js) I suggest you sit down and read the docs. Sounds deathly boring, but 75% of the battle with data transforms is knowing what's in your toolbox. Note how the English description of the process matches almost exactly with the example code (filter, some, includes).
var Lands = [{
'LandId': '123',
'something1': 'qwerty',
'something2': 'qwerty',
'Blocks': [{
'id': '456',
'LandId': '123'
}, {
'BlockId': '789',
'LandId': '123'
}]
}
];
// assuming lands is like the above example
// and landIDs is an array of strings
var selectLandsWithBlocks = function(lands, landIDs) {
return _.filter(lands, function(land) {
var blocks = land.Blocks;
var blockHasALandId = function(block) {
return _.includes(landIDs,block.LandId);
};
return _.some(blocks, blockHasALandId);
});
};
console.log(selectLandsWithBlocks(Lands,[]));
console.log(selectLandsWithBlocks(Lands,['mittens']));
console.log(selectLandsWithBlocks(Lands,['123']));
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>

Executing a list of functions on an array

I'm getting to grips with using functional programming beyond a simple map or two. I have a situation where I want to be able to filter some elements from an array of objects, based on a particular field of those objects. It's a contrived example, but here goes:
I have a list of field definitions, and I want to extract two of them based on their title.
const toSearch = [
{ title: "name", description: "Their name" },
{ title: "age", description: "Their age" },
{ title: "gender", description: "Their gender" }
]
const fieldsToFind = ["name", "age"]
let filterObjects = R.map(R.objOf('title'), fieldsToFind)
let filterFuncs = R.map(R.whereEq(R.__), filterObjects)
let found = R.map(R.filter, filterFuncs)
console.log("Filter objects:", JSON.stringify(filterObjects))
console.log("Filter functions:", JSON.stringify(filterFuncs))
console.log("Found:", found[0](toSearch))
console.log("Found:", found[1](toSearch))
If I run this, the last of the output is the two elements of toSearch that I'm looking for, but it's not exactly neat. I've been trying to get another map working to get around executing found elements manually, but I also feel that even leading up to that point I'm taking an overly circuitous route.
Although it's a contrived example, is there a neater way of accomplishing this in a functional style?
One fairly simple way of doing this is:
R.filter(R.where({R.title: R.contains(R.__, ['name', 'age'])}))(toSearch);
//=> [
// {"description": "Their name", "title": "name"},
// {"description": "Their age", "title": "age"}
// ]
or, equivalently,
R.filter(R.where({title: R.flip(R.contains)(['name', 'age'])}))(toSearch);
One advantage, especially if you import the relevant functions from R into your scope, is how closely this reads to your problem domain:
var myFunc = filter(where({title: contains(__, ['name', 'age'])}));
myFunc = filter where the title contains 'name' or 'age'.
You can see this in action on the Ramda REPL.
I have a situation where I want to be able to filter some elements
from an array of objects, based on a particular field of those
objects.
For your situation, its quite simple
var filteredItems = toSearch.filter(function(value){ return fieldsToFind.indexOf(value.title) != -1 });
You could use .reduce to accomplish your task. MDN has a very brief explanation of the reduce function

Categories

Resources