Reputation: 23
I am using a library that returns a series of values in a slightly unconventional way. Rather than returning an array the library returns nested objects with the final leaf node included as a value. For example:
red.walk.fast.true
becomes {red: {walk: {fast: 'true'}}}
or
climbing.ascending
becomes {climbing: 'ascending'}
The format of nested objects actually suits my use case but I need a way of converting the final value into another nested object (with a null value - but this is not important) rather than a value. So:
{red: {walk: {fast: 'true'}}}
becomes {red: {walk: {fast: {true: null}}}}
The formula needs to work for any arbitrlily deeply nested object. I am struggling to get a recursive function working with Ramda though. I thought the following would work:
const stringPropToObject = R.ifElse(
R.is(String),
R.assoc(R.__, null, {}),
mapAllStringPropsToObjs);
const mapAllStringPropsToObjs = R.map(stringPropToObject)
const updatedObject = mapAllStringPropsToObjs({ a: { b: { c: 'd' } } })
console.log(updatedObject);
mapAllStringPropsToObjs (attempts) to map over the objects properties passing them to stringPropToObject. This then looks at the property passed in.
This results in "Cannot access 'mapAllStringPropsToObjs' before initialization". I understand why I am getting this error with the current order of declarations but I don't see how I can order them to avoid it - they are co-dependant functions.
Anyone know what I could do using Ramda or vanilla JS to be able to convert:
{climbing: 'ascending'}
into {climbing: {ascending: null}}
and
{red: {walk: {fast: 'true'}}}
into {red: {walk: {fast: {true: null}}}}
or any other arbitrarily nested value.
Thanks in advance for any suggestions.
Upvotes: 1
Views: 625
Reputation: 50807
The simplest way to fix your co-dependent functions issue is to simply replace the reference in the first one to the second with a lambda:
const stringPropToObject = ifElse(
is(String),
assoc(__, null, {}),
x => mapAllStringPropsToObjs(x)
);
const mapAllStringPropsToObjs = map(stringPropToObject)
const updatedObject = mapAllStringPropsToObjs({ a: { b: { c: 'd' } } })
console.log (updatedObject)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {ifElse, is, assoc, __, map} = R </script>
Ramda isn't particularly designed to work well with recursion, but this is of course a basic issue in the language.
I would probably have written this differently, in vanilla JS, with something like this:
const transform = (obj) =>
Object .entries (obj)
.reduce ((a, [k, v]) => ({
...a,
[k]: typeof v == 'string' ? {[v]: null} : transform (v)
}), {})
But I think that your recursive Ramda solution with this minor tweak is simpler and cleaner.
Upvotes: 1
Reputation: 193087
As stated by @ScottSauyet you can use a function (anonymous or arrow) to call mapAllStringPropsToObjs()
in the future when it's actually defined.
A piece of code that performs a delayed computation is called a thunk. According to wikipedia:
In computer programming, a thunk is a subroutine used to inject an additional calculation into another subroutine. Thunks are primarily used to delay a calculation until its result is needed, or to insert operations at the beginning or end of the other subroutine.
In addition, I would also replace R.assoc with a flipped R.objOf to generate the object.
const objOfNull = flip(objOf)(null);
const mapAllStringPropsToObjs = map(ifElse(
is(String),
objOfNull,
x => mapAllStringPropsToObjs(x)
));
const updatedObject = mapAllStringPropsToObjs({ a: { b: { c: 'd' } } })
console.log (updatedObject)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {map, ifElse, is, flip, objOf} = R </script>
Upvotes: 1
Reputation: 7443
This solution is recursive and creates a new converted object instead of altering the old one.
function transformObject(obj, final = {}, curr = final) {
Object.entries(obj).forEach(([key, value]) => {
curr[key] = {}
if (typeof value !== 'object') curr[key][value] = null
else transformObject(value, final, curr[key])
})
return final
}
const dataOne = {climbing: {run: 'slow', hike: {mountain: 'fast'}}}
const dataTwo = {red: {walk: {fast: 'true'}}}
console.log(dataOne, transformObject(dataOne))
console.log(dataTwo, transformObject(dataTwo))
Upvotes: 0
Reputation: 386858
You could iterate the entries and if no object, assign a new one.
function f(object) {
Object.entries(object).forEach(([k, v]) => {
if (v && typeof v === 'object') f(v);
else object[k] = { [v]: null };
});
}
var object = { red: { walk: { fast: 'true' } } };
f(object);
console.log(object);
Upvotes: 1