Transform a value nested at any depth into an object key using Ramda or plain JavaScript

I have encountered a unique scenario while using a specific library. Instead of returning an array, this library returns nested objects with the final leaf node containing the value. For instance:

red.walk.fast.true is returned as {red: {walk: {fast: 'true'}}} or climbing.ascending is returned as {climbing: 'ascending'}

The current format of nested objects serves my purpose, but I require a method to convert the final value into another nested object with a null value. Therefore:

{red: {walk: {fast: 'true'}}} should become

{red: {walk: {fast: {true: null}}}}

This transformation should work for any deeply nested object. I am facing challenges implementing a recursive function using Ramda for this task. I tried the following approach:

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 iterate over an object's properties and pass them to stringPropToObject. The function then evaluates the property being passed in.

  • If the property is a string, it returns a new object with that string.
  • If the property is an object, it should recursively call mapAllStringPropsToObjs until a string value is encountered.

However, I am encountering an error "Cannot access 'mapAllStringPropsToObjs' before initialization." I understand why this error is occurring due to the current order of declarations, but I am unsure how to reorder them to resolve the issue as both functions are interdependent.

Is there any advice on using Ramda or vanilla JS to convert: {climbing: 'ascending'} to {climbing: {ascending: null}} and {red: {walk: {fast: 'true'}}} to

{red: {walk: {fast: {true: null}}}}
or any other nested value with arbitrary depth?

I appreciate any suggestions provided. Thank you.

Answer №1

To overcome the issue with your co-dependent functions, a straightforward solution is to substitute the reference in the first function with a lambda expression that points to the second one:

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 is not optimized for recursive operations, but this is a common challenge in the language.

If it were up to me, I would have approached this problem differently using vanilla JavaScript, like in the following example:

const transform = (obj) => 
  Object.entries(obj) 
    .reduce((acc, [key, value]) => ({
      ...acc,
      [key]: typeof value === 'string' ? {[value]: null} : transform(value)
    }), {})

However, your modified recursive Ramda solution appears to be a more straightforward and elegant approach.

Answer №2

To handle the entries one by one, you can check if an object exists and create a new one if it doesn't.

function iterate(object) {
    Object.entries(object).forEach(([key, value]) => {
        if (value && typeof value === 'object') iterate(value);
        else object[key] = { [value]: null };
    });
}

var obj = { blue: { run: { slow: 'true' } } };

iterate(obj);
console.log(obj);

Answer №3

ScottSauyet mentioned the option of using a function, whether anonymous or arrow, to invoke mapAllStringPropsToObjs() at a future point when it has been defined.

A piece of code that delays a computation is known as a thunk. As per Wikipedia:

In the realm of computer programming, a thunk is a subroutine employed to insert an additional calculation into another subroutine. Thunks serve the purpose of postponing a calculation until its outcome is required, or to incorporate operations at the start or end of the aforementioned subroutine.

Moreover, it is suggested to substitute R.assoc with a flipped R.objOf in order 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>

Answer №4

This is a recursive approach that generates a new transformed object without modifying the original one.

function convertObj(obj, result = {}, current = result) {
  Object.entries(obj).forEach(([key, value]) => {
    current[key] = {}
    if (typeof value !== 'object') current[key][value] = null
    else convertObj(value, result, current[key])
  })
  return result
}

const sampleData1 = {climbing: {run: 'slow', hike: {mountain: 'fast'}}}
const sampleData2 = {red: {walk: {fast: 'true'}}}

console.log(sampleData1, convertObj(sampleData1))
console.log(sampleData2, convertObj(sampleData2))

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Exploring ways to extract HREF using Selenium in combination with Node JS

I am having trouble extracting the hrefs from my web element using .getAttribute("href"). It works fine when applied to a single variable, but not when looping through my array. const {Builder, By, Key, until} = require('selenium-webdriver'); (a ...

How can I disable auto-fill for password input fields? Setting autocomplete="off" doesn't seem to be working

Hey there, I'm having some trouble with the autocomplete feature in Google Chrome. Can anyone suggest an alternative way to disable autocomplete for password fields? By the way, my project is using Vue.js. ...

Step-by-step guide on integrating async and await functionality into Vuetify rules using an ajax call

I'm currently using Vuetify form validation to validate an input field, and I'm exploring the possibility of making an ajax get call await so that I can utilize the result in a rule. However, my attempts at this approach have not been successful! ...

considering multiple website addresses as one collective entity for the Facebook like button

Currently, I am tackling an issue on a website that employs a custom CMS which automatically generates URLs based on the titles of articles. The main problem faced by our contributors is that when they update the title of an article, it creates a new URL ...

Navigate back to the previous page following the completion of an AJAX data submission

When using ajax to send data from page A to the server, the spring controller returns welcome page B. This process works correctly on Firefox and Internet Explorer, but on Chrome, there is an issue where after successfully sending the data via ajax, the de ...

Ways to protect my login details when making an ajax request?

The scenario I am dealing with is as follows: I have developed a website using Javascript where users are required to input a username and password. Subsequently, the site makes an ajax call to the Webserver. On the other end, I have a PHP-powered Webser ...

Creating a navbar that sticks to the top of the page

I've been working on some code to create a sticky navbar that remains fixed as I scroll down, but unfortunately, it's not working. I suspect the issue might lie in the CSS, as the HTML and javascript seem fine. Interestingly, this same code has w ...

Update D3 data, calculate the quantity of rows in an HTML table, and add animations to SVGs in the final

Attempting to update data in an HTML table using D3 has proven to be quite challenging for me. My task involves changing the data in multiple columns, adjusting the number of rows, and animating SVG elements in each row based on new data arrays. Despite tr ...

Switch the Ionic Slide Box to the "does-continue" setting

Currently in the process of constructing a slide-box containing numerous items. I've borrowed the code from the example provided here for an infinite number of items, and it's performing well. However, I have a considerable yet finite amount of ...

Automatically hide a label after a certain amount of time following a button click

Currently, I am working with ASP.NET and C#. Within my application, there is a registration form that includes a label to display the status of registration, either success or failure. The content of this label is determined dynamically in the codebehind ...

The output returned by an AngularJS controller

John Papa introduced the 'controller as' technique for AngularJS in his article titled Do You Like Your Angular Controllers with or without Sugar: myApp.controller("MainCtrl", [ function () { var vm = this; // using ViewModel conv ...

Is it possible to inject JavaScript into the DOM after it has been loaded using an AJAX call?

I have a specific div element identified by the id #id1 that contains clickable links. Upon clicking on these links, an AJAX call is made to retrieve additional links from the server. My current approach involves replacing the existing links within #id1 w ...

Splitting Angular modules into separate projects with identical configurations

My Angular project currently consists of approximately 20 different modules. Whenever there is a code change in one module, the entire project needs to be deployed. I am considering breaking down my modules into separate projects for individual deployment. ...

Tips for storing the state using Localstorage?

Greetings to the person reading this message! I am relatively new to programming and I have a query... I created a Navigation bar: body > div > nav > div > ul > li*10 I have implemented the styling in CSS. //Navigation Bar const list = ...

I'm encountering an issue with the submission button in the wizard, I suspect it could be a problem

The submit button is not working on this wizard. I believe the issue lies within the JavaScript of the form wizard. When I click the submit button, nothing happens. Upon inspecting the submit button code, I found it to be like this... <a href="#finish" ...

What steps should I follow to run my JavaScript application locally on Linux Mint?

Currently, I am diligently following a tutorial and ensuring that each step is completed accurately. My goal is to locally host my javascript app at localhost:3000. Unfortunately, I am facing difficulties as every attempt to run npm run dev results in an e ...

What steps can I take to fix the sum function?

My function calculates heart rate targets for sports, but there seems to be an issue in the switch statement. The final sum in the function is not being computed properly. For example, if someone is 20 years old with a resting heart rate of 50 beats per mi ...

In HTML, data can be easily accessed, however, JavaScript does not have the same

When trying to access the 'data' element in a JSON object, I have encountered an issue. The element is accessible when called from HTML, but not when called in JavaScript. HTML: <p>{{geoJson.data}}</p> JavaScript: let scope; let d ...

Integrate your React Native application with a Node.js backend on your local machine

My attempt to establish a connection between my react native app and my node.js app on a Windows system has hit a roadblock. While I am able to receive responses from the node API using Postman, the response from the react native app is coming back as unde ...

Transforming ASP.NET MVC IEnumerable view model into a JSON array of elements

My goal is to develop a dynamic calendar in ASP.NET MVC that pulls event data from a database to populate it. Right now, the calendar is set up to read a json array of objects, but I am facing an issue with converting my ViewModel data into a format that t ...