1

I have an array of structured strings with have connection | as a format which self-divided into levels. I want to convert it into a structured object with multiple levels.

Input:

[
  "clothes|tshirt|tshirt-for-kids",
  "clothes|coat|raincoat",
  "clothes|coat|leather-coat",
  "clothes|tshirt|tshirt-for-men",
  "clothes|tshirt|tshirt-for-men|luxury-tshirt",
]

Expected output:

{
   clothes: {
     tshirt: {
       tshirt-for-kids: {},
       tshirt-for-men: {
         luxury-tshirt: {}
       }
     },
     coat: {
       raincoat: {}
       leather-coat: {}
     }
   }
}
5
  • 1
    You can use the addPath() idea from Scott's answer here, like so: jsfiddle.net/y9zckfh1 Commented Apr 18, 2021 at 8:00
  • @NickParsons can you post it as an answer? Commented Apr 18, 2021 at 8:51
  • It's mainly Scott's code so I don't want to post it as my own (as a result, I'll just keep it as a comment) Commented Apr 18, 2021 at 9:01
  • Scott's answer should be the best answer for my question. Thank you very much Commented Apr 18, 2021 at 9:05
  • No worries, you can close your question as a duplicate of How to convert a single dimensional object to a multi-dimensional object in JavaScript? if you feel as though the answer there answers your question Commented Apr 18, 2021 at 9:37

5 Answers 5

2

Very simple task - just enumerate the array and create the relevant object keys:

var myArray = [
  "clothes|tshirt|tshirt-for-kids",
  "clothes|coat|raincoat",
  "clothes|coat|leather-coat",
  "clothes|tshirt|tshirt-for-men",
  "clothes|tshirt|tshirt-for-men|luxury-tshirt",
]

var result = {}, levels, current, temp;
while(myArray.length > 0)
{
  levels = myArray.pop().split('|');
  temp = result;
  while(levels.length > 0)
  {
    current = levels.shift();
    if(!(current in temp)) temp[current] = {};
    temp = temp[current];
  }
}
console.log(result);

Sign up to request clarification or add additional context in comments.

Comments

1

You could try this:

const input = [
  "clothes|tshirt|tshirt-for-kids",
  "clothes|coat|raincoat",
  "clothes|coat|leather-coat",
  "clothes|tshirt|tshirt-for-men",
  "clothes|tshirt|tshirt-for-men|luxury-tshirt",
];


function convertStrToObject(str, sep, obj) {
  const sepIndex = str.indexOf(sep);
  if (sepIndex == -1) {
    obj[str] = obj[str] || {};
  } else {
    const key = str.substring(0, sepIndex);
    obj[key] = obj[key] || {};
    convertStrToObject(str.substring(sepIndex + 1), sep, obj[key]);
  }
}

const all = {};
for (let i = 0; i < input.length; ++i) {
  convertStrToObject(input[i], "|", all);
}
console.log(all);

2 Comments

Wouldn't be better if we use obj[key] ??= {}; instead obj[key] = obj[key] || {};
@decpk: Basically I agree. However non nullish coalescing is not supported by IE and since we are dealing with only objects in this example, it makes no difference.
1

Assuming you intend to collect properties, all having an empty object as leaf node.

// input
const input = [
  "clothes|tshirt|tshirt-for-kids",
  "clothes|coat|raincoat",
  "clothes|coat|leather-coat",
  "clothes|tshirt|tshirt-for-men",
  "clothes|tshirt|tshirt-for-men|luxury-tshirt",
];

// Here, we collect the properties
const out = {};

// map the input array, splitting each line at |
input.map(i => i.split('|'))
.filter(a => a.length > 0) // lets not entertain empty lines in input
.forEach(a => { // process each array of property names
   // start at outermost level
   let p = out;

   // iterate properties
   for(const v of a){
      // create a property if it is not already there
      if(!p.hasOwnProperty(v)){
         p[v] = {};
      }
      // move to the nested level
      p = p[v];
   }
});

// lets see what we have created
console.log(out);

Comments

1

A number of solutions have been suggested already, but I'm surprised none involves reduce() - which would seem the more idiomatic solution to me.

var array = [
    "clothes|tshirt|tshirt-for-kids",
    "clothes|coat|raincoat",
    "clothes|coat|leather-coat",
    "clothes|tshirt|tshirt-for-men",
    "clothes|tshirt|tshirt-for-men|luxury-tshirt",
]

var object = array.reduce(function (object, element) {
    var keys = element.split("|")

    keys.reduce(function (nextNestedObject, key) {
        if (!nextNestedObject[key]) nextNestedObject[key] = {}
        return nextNestedObject[key]
    }, object)

    return object
}, {})

console.log(object)

Comments

0

One Liner With eval

Used eval to evaluate strings like the following:

'o["clothes"]??={}'
'o["clothes"]["tshirt"]??={}'
'o["clothes"]["tshirt"]["tshirt-for-kids"]??={}'

const 
  data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat", "clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
      
  arr = data.map((d) => d.split("|")),
  res = arr.reduce((r, a) => (a.forEach((k, i) => eval(`r["${a.slice(0, i + 1).join('"]["')}"]??={}`)), r), {});

console.log(res)

One Liner Without eval

const 
  data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat","clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
      
  res = data.reduce((r, d) => (d.split("|").reduce((o, k) => (o[k] ??= {}, o[k]), r), r), {});

console.log(res)

5 Comments

Stop using eval on external input. You are allowing someone to run arbitrary JS code via the input.
While this might work please don't write code like this. It might be just one line but next time you look at this code you need more time to understand it and your colleagues are going to hate you for writing code like this.
@KrisztiánBalla Ya totally agreed, this is just to add some versatility to the answers.
@S.D. Ya I know, we can always sanitize before using eval.
@KrisztiánBalla @S.D. Added solution w/o eval as well.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.