Create a layered structure using a specified path

I am aiming to create a function that can accept an object path, like

{ 'person.data.info.more.favorite': 'smth' }

and then output a nested object:

{
  person: {
    data: {
      info: {
        more: {
          favorite: 'smth',
        },
      },
    },
  },
}

What is the easiest way to accomplish this?

Answer №1

When dealing with a single key/value pair, you can use a simple method called `nest` which involves using the functions `split` and `reduceRight` -

const nest = ([path, value]) =>
  path.split('.').reduceRight((v, p) => ({ [p]: v }), value)

console.log(nest(['a', 'smth']))
console.log(nest(['a.b', 'smth']))
console.log(nest(['person.data.info.more.favorite', 'smth']))

{
  "a": "smth"
}
{
  "a": {
    "b": "smth"
  }
}
{
  "person": {
    "data": {
      "info": {
        "more": {
          "favorite": "smth"
        }
      }
    }
  }
}

If you have to apply this method to an entire object where key paths could overlap, you'll need to use a function called `expand` which utilizes `nest` -

const input = {
  "server.host": "localhost",
  "server.port": 9999,
  "database.host": "localhost",
  "database.name": "mydb",
  "database.port": 7777,
  "database.user": "root",
  "debug.log": true
}

console.log(expand(input))
{
  "server": {
    "host": "localhost",
    "port": 9999
  },
  "database": {
    "host": "localhost",
    "name": "mydb",
    "port": 7777,
    "user": "root"
  },
  "debug": {
    "log": true
  }
}

To implement this on a complete object, you can use the `expand(o)` function which takes entries from the input object `o`, maps over each with `nest`, and finally reduces the result with `merge` -

const expand = o =>
  Object.entries(o).map(nest).reduce(merge, {})

const nest = ([path, value]) =>
  path.split(".").reduceRight((v, p) => ({ [p]: v }), value)

// merge function definition here... (omitted for brevity)

const input = {
  "server.host": "localhost",
  "server.port": 9999,
  "database.host": "localhost",
  "database.name": "mydb",
  "database.port": 7777,
  "database.user": "root",
  "debug.log": true
}

console.log(expand(input))

Answer №2

CORRECTION: Below is the accurate solution, sorry for misreading it initially.

let myObj = { 'person.data.info.more.favorite': 'smth' }

function getNestedObj( object ) {

  let keys = Object.keys( object )[0];
  let value = Object.values( object )[0];
  let keyArray = keys.split(".");

  let nextObject;
  let returnObj = {};
  

  keyArray.forEach( ( key ) => {
 
    let newObj = {};

    if( !nextObject ) {
      returnObj[key] = newObj
      nextObject = newObj;
    } else {
      nextObject[key] = newObj
      nextObject = newObj;
    }
    console.log(key)
  });

  nextObject[0] = value

  console.log( returnObj );

  return returnObj;
}

getNestedObj( myObj);

PREVIOUS INCORRECT ANSWER:

Here's a code snippet to achieve the desired result. Note that it only works with string values in this particular implementation

"use strict";
let myObject = {
    person: {
        data: {
            info: {
                more: {
                    favorite: 'smth',
                },
                less: {
                    favourite: 'smthing else'
                },
                random: "a string"
            },
        },
    },
};
function concatKeys(object, key) {
    let keys = Object.keys(object);
    let keyDictionary = {};
    if (key && typeof object === 'string') {
        return { [key]: object };
    }
    if (keys.length > 0) {
        keys.forEach((nextKey) => {
            let newKey = key ? `${key}.${nextKey}` : nextKey;
            let nextObj = object[nextKey];
            let newDictionary = concatKeys(nextObj, newKey);
            keyDictionary = Object.assign(Object.assign({}, keyDictionary), newDictionary);
        });
    }
    return keyDictionary;
}
let output = concatKeys(myObject);
console.log(output);

You can check the code in action here on Typescript playground

If you run the aforementioned code, it will display the following in the console.

[LOG]: {
  "person.data.info.more.favorite": "smth",
  "person.data.info.less.favourite": "smthing else",
  "person.data.info.random": "a string"
} 

Edit: Apologies for the confusion earlier, I've updated the answer to be in JavaScript now while the playground remains in Typescript but the provided code above is in JS.

Answer №3

The approach presented here shares similarities with Mulan's answer, but takes a different approach to dividing responsibilities. It introduces an intermediate format referred to as pathEntries, which resembles the output of Object.entries but includes complete paths instead of just top-level properties. In this example, it appears as follows:

[['person', 'data', 'info', 'more', 'favorite'], 'smth']

The expand function utilizes Object.entries on the input, splits keys based on dots, and then applies hydrate to the resultant data. The hydrate function, a fundamental aspect of my toolset, acts as a thin covering for setPath, consolidating path entries into a single object. Its structure is demonstrated below:

const setPath = ([p, ...ps]) => (v) => (o) =>
  p == undefined ? v : Object.assign(
    Array.isArray(o) || Number.isInteger(p) ? [] : {},
    {...o, [p]: setPath(ps)(v)((o || {})[p])}
  )

const hydrate = (xs) =>
  xs.reduce((a, [p, v]) => setPath(p)(v)(a), {})

const expand  = (o) =>
  hydrate(Object.entries(o).map(([k, v]) => [k.split('.'), v]))

console.log(expand({'person.data.info.more.favorite': 'smth'}))

const input = {"server.host": "localhost", "server.port": 9999, "database.host": "localhost", "database.name": "mydb", "database.port": 7777, "database.user": "root", "debug.log": true}

console.log(expand(input))
.as-console-wrapper {max-height: 100% !important; top: 0}

An advantage of this intermediary format is its ability to represent arrays, using integers for indices and strings for object attributes:

hydrate([
  [[0, 'foo', 'bar', 0, 'qux'], 42], 
  [[0, 'foo', 'bar', 1, 'qux'], 43], 
  [[0, 'foo', 'baz'], 6],
  [[1, 'foo', 'bar', 0, 'qux'], 44], 
  [[1, 'foo', 'bar', 1, 'qux'], 45], 
  [[1, 'foo', 'baz'], 7],  
]) //=> 
// [
//   {foo: {bar: [{qux: 42}, {qux: 43}], baz: 6}}, 
//   {foo: {bar: [{qux: 44}, {qux: 45}], baz: 7}}
// ]

To utilize this feature effectively, we might require a more intricate string format like { '[0].foo.bar[0].qux': 42 } along with a parser that surpasses k.split ('.'). Although not overly complex, addressing the more sophisticated system permitting arbitrary string keys – including quotes, brackets, and periods – would lead us down a more elaborate path.

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

A step-by-step guide on setting up flow types for @material-ui/core version 4

Is there a way to install flow types for material-ui/core version 4.x.x? It seems like the last update was for version 1.x.x here. The documentation on this topic is quite limited here. I'm unsure if there is still support for this, especially since t ...

What is the best way to vertically align an InputLabel within a MUI Grid item?

I'm trying to vertically center the InputLabel inside a MUI Grid item. Here's what I've attempted: import { FormControl, Grid, Input, InputLabel, TextField } from "@mui/material"; export default function App() ...

What is the reason for multiple ajax functions being triggered when submitting a form through ajax?

I have a Drupal form with an AJAX submit. Additionally, I have another jQuery $.get function that sends a request every 2 minutes and inserts the response into an HTML element. The form and this JavaScript code are independent of each other, performing sep ...

Navigating the world of gtag and google_tag_manager: untangling

Tracking custom events in my react application using Google Analytics has been successful. Initially, I followed a helpful document recommending the use of the gtag method over the ga method for logging calls. The implementation through Google Tag Manager ...

Recognize different HTML components when an event occurs

My application is equipped with a multitude of buttons, inputs, and other elements that trigger different events. I am looking for a way to easily differentiate between each element and the event it triggers. For instance, consider the following snippet f ...

Error: npx is unable to locate the module named 'webpack'

I'm currently experimenting with a customized Webpack setup. I recently came across the suggestion to use npx webpack instead of node ./node_modules/webpack/bin/webpack.js. However, I am encountering some difficulties getting it to function as expecte ...

Angular has the ability to round numbers to the nearest integer using a pipe

How do we round a number to the nearest dollar or integer? For example, rounding 2729999.61 would result in 2730000. Is there a method in Angular template that can achieve this using the number pipe? Such as using | number or | number : '1.2-2' ...

Arranging JSON information based on category

I am currently populating an HTML table with data retrieved from a JSON file. It currently displays all the data in the order it appears in the JSON file, but I would like to organize it into different tables based on the "group" category in the file such ...

What is the best way to initiate the handling of newly inserted values in a Vuex store?

I am working with a Vuex store that stores entries: const store = createStore({ state() { return { entries: [ { id: 1, date-of-birth: "2020-10-15T14:48:00.000Z", name: "Tom", }, ...

Warning: The use of 'node --inspect --debug-brk' is outdated and no longer recommended

Encountering this error for the first time, please forgive any oversight on my part. The complete error message I am receiving when running my code is: (node:10812) [DEP0062] DeprecationWarning: `node --inspect --debug-brk` is deprecated. Please use `node ...

Creating a sticky header for a MatTable in Angular with horizontal scrolling

Having an issue with merging Sticky Column and horizontal scrolling. Check out this example (it's in Angular 8 but I'm using Angular 10). Link to Example The base example has sticky headers, so when you scroll the entire page, the headers stay ...

Dynamic Visibility Control of GridPanel Header in ExtJS

I have a GridPanel that needs to display a limited number of resources. If there are more resources available than what is currently shown, I would like the panel's header/title to indicate this by displaying text such as "more items available - displ ...

"Facing an issue with Google Chrome not refreshing the latest options in an HTML select dropdown list

Having trouble creating an offline HTML file that incorporates jQuery for the script. The page features a state select menu followed by a second menu for counties. When a state is selected, only the corresponding counties should display while others remai ...

Extracting Querystring Value in C#

When using the code below to set the iframe src with JavaScript, everything works as expected. However, in the C# code behind, I am receiving the query string in a format like this: id=Y&amp%3bcust_id=100&amp%3. Is there a way to simplify this? v ...

The state value is not preserved after redirecting to another page

For the past couple of days, I've been struggling with an issue and I can't seem to figure out what I'm doing wrong. My goal is to create a login page that redirects users to the dashboard after they log in. To maintain consistency acros ...

Expanding/Combining entities

I'm facing an issue while trying to Extend/Push/Merge an object using AngularJS. The problem arises when I attempt to extend the object, as it adds a new object with an Index of 0 and subsequent additions also receive the same index of 0. //Original ...

"Incorporate keyframes to create a mouseleave event with a distinctive reverse fade

After posing a similar question a few days back, I find myself encountering yet another hurdle in my project. The task at hand is to switch the background image when hovering over a button with a fade-in effect using @keyframes. However, I'm stuck bec ...

Toggle visibility of a div with bootstrap checkbox - enforce input requirements only if checkbox is marked

Lately, I've been facing a strange issue with using a checkbox that collapses a hidden div by utilizing bootstrap. When I include the attribute data-toggle="collapse" in the checkbox input section, the div collapses but it mandates every single one o ...

Inconsistencies in grunt-ng-constant target operations

I encountered a strange issue with grunt-ng-constant where only 2 out of the 3 targets are working. Here is how my configuration is set up: grunt.initConfig({ ngconstant: { options: { space: ' ', wrap: '"use strict";&bso ...

How to organize initial, exit, and layout animations in Framer Motion (React) tutorial?

Currently, I am utilizing framer-motion library for animating a change in grid columns. This is the objective: Within the grid container (#db-wrapper), there are nine buttons arranged. https://i.stack.imgur.com/61pQqm.png When the user switches to the ...