0

The object I must parse:

const object = {
"item1": "value",
"item2/0/subitem1": "value",
"item2/0/subitem2": "value",
"item2/1/subitem1": "value",
"item2/1/subitem2": "value",
"item3/0/subitem1/subsubitem1": "value",
"item3/1/subitem1/subsubitem1": "value",
"item4/0/subitem1/0/subsubitem1": "value",
"item4/0/subitem1/1/subsubitem1": "value",
}

I would like to parse it into a nested object that looks like this:

const parsedObject = {
item1: value,
item2: [
  {
    subitem1: value,
    subitem2: value,
  },
  {
    subitem1: value,
    subitem2: value,
  }
],
item3: [
  {
    subtiem1: {
      subsubitem1: value,
    }
  },
  {
    subtiem1: {
      subsubitem1: value,
    }
  },
],
item4: [
  {
    subitem1: [
      {
        subsubitem1: value,
      },
      {
        subsubitem1: value,
      },
    ]
  }
]

}

I have tried to write this function, but I'm stuck and do not know how to continue with it.

const object = {
  "item1": "value",
  "item2/0/subitem1": "value",
  "item2/0/subitem2": "value",
  "item2/1/subitem1": "value",
  "item2/1/subitem2": "value",
  "item3/0/subitem1/subsubitem1": "value",
  "item3/1/subitem1/subsubitem1": "value",
  "item4/0/subitem1/0/subsubitem1": "value",
  "item4/0/subitem1/1/subsubitem1": "value",
}

const mainFunction = () => {
  const specie = {};
  const map = new Map();
  Object.keys(object).forEach((key) => {
    const keyTokens = key.split('/');
    processKeyValues(specie, keyTokens, object[key], map);
  });
  console.log(specie);
}

const convertStringToBoolean = (str: string): string | boolean => {
  return str.toLowerCase() === 'true' ? true : str.toLowerCase() === 'false' ? false : str;
};

const processKeyValues = (specie: any, keyTokens: any[], value: string, map: Map < any, any > ): any => {
  keyTokens.reduce((prev, curr, index, array) => {
    if (index === array.length - 1) {
      const valuePossibleNumber = parseInt(value); //transform number strings into numbers
      if (!isNaN(valuePossibleNumber)) {
        prev[curr] = valuePossibleNumber;
        return;
      }
      prev[curr] = convertStringToBoolean(value); //boolean strings into booleans
      return;
    }
    const currentKeyPossibleNumber = parseInt(curr);
    const currentKeyIsNumber = !isNaN(currentKeyPossibleNumber);
    const nextKeyPossibleNumber = parseInt(array[index + 1]);
    const nextKeyIsNumber = !isNaN(nextKeyPossibleNumber);

    if (currentKeyIsNumber) {
      const hasMappedIndex = map.has(currentKeyPossibleNumber);
      let mappedIndex = map.get(currentKeyPossibleNumber);
      if (!hasMappedIndex) {
        mappedIndex = map.size;
        map.set(currentKeyPossibleNumber, mappedIndex);
      }
      const currentKeyAsIndexDoesNotExist = !prev[mappedIndex];
      if (currentKeyAsIndexDoesNotExist) {
        if (nextKeyIsNumber) {
          prev.push([]);
        } else {
          prev.push({});
        }
      }
      return prev[mappedIndex];
    }
    const currentKeyDoesNotExist = !prev[curr];
    if (currentKeyDoesNotExist) {
      if (nextKeyIsNumber) {
        prev[curr] = [];
        map.clear();
      } else {
        prev[curr] = {};
      }
    }
    return prev[curr];
  }, specie);
};

The reason I use a map for the array indexes is that it may be that I do not receive those indexes necessarily in order like 0,1,2 etc., but also like 0,1,10,101 etc. and I access that index in the array I am creating because it is not that large.

Seeing that I am receiving these objects from an endpoint I cannot modify, it would be ideal if I could find a way to generalize the code to cover situations when I could get multiple nested arrays ans objects so I thought a recursive function would be best suited, but I cannot figure out how, thus I would be thankful if you could help me out.

2
  • 1
    I have recently answered similar question. Does it help? Commented Sep 6, 2022 at 16:09
  • It did not entirely solve my issue, but it gave me ideas which I want to try and I will come back with an answer whether I still need help. Thanks! Commented Sep 7, 2022 at 9:36

1 Answer 1

3

I'll try again. Lets re-use that solution. It might not be the shortest way, but why not.

Here is the old one: Check out the output.

const object = {
  "item1": "value",
  "item2/0/subitem1": "value",
  "item2/0/subitem2": "value",
  "item2/1/subitem1": "value",
  "item2/1/subitem2": "value",
  "item3/0/subitem1/subsubitem1": "value",
  "item3/1/subitem1/subsubitem1": "value",
  "item4/0/subitem1/0/subsubitem1": "value",
  "item4/0/subitem1/1/subsubitem1": "value",
}

let agg = {
  temp: []
};

Object.entries(object).forEach(([path, value]) => {
  path.split('/').reduce((agg, part, level, parts) => {
    if (!agg[part]) {
      agg[part] = {
        temp: []
      };
      agg.temp.push({
        id: parts.slice(0, level + 1).join("/"),
        level: level + 1,
        children: agg[part].temp
      })
      // console.log(agg)
    }
    return agg[part];
  }, agg)
})

var result = agg.temp;
console.log(result)
.as-console-wrapper {
  max-height: 100% !important
}

Now we want to transform that output into the expected output. This is done using a recursion (arr_tree_to_obj) which works by iterating entire intermediate tree and creating the proper key: value pair on the target object.

const object = {
  "item1": "value",
  "item2/0/subitem1": "value",
  "item2/0/subitem2": "value",
  "item2/1/subitem1": "value",
  "item2/1/subitem2": "value",
  "item3/0/subitem1/subsubitem1": "value",
  "item3/1/subitem1/subsubitem1": "value",
  "item4/0/subitem1/0/subsubitem1": "value",
  "item4/0/subitem1/1/subsubitem1": "value",
}

function flat_directory_list_to_directory_tree(list) {
  let agg = {
    temp: []
  };

  Object.entries(object).forEach(([path, value]) => {
    path.split('/').reduce((agg, part) => {
      if (!agg[part]) {
        agg[part] = {
          temp: []
        };
        agg.temp.push({
          id: part,
          value: value,
          children: agg[part].temp
        })
      }
      return agg[part];
    }, agg)
  })

  var mid = agg.temp;
  // console.log(mid)

  function arr_tree_to_obj(arr) {
    var result = arr.reduce(function(agg, item) {
      if (!item.children.length) {
        agg[item.id] = item.value
      } else {
        agg[item.id] = arr_tree_to_obj(item.children);
      }
      return agg;
    }, {})
    return result;
  }

  return (arr_tree_to_obj(mid))
}

var parsedObject = flat_directory_list_to_directory_tree(object);
console.log(parsedObject)
.as-console-wrapper {
  max-height: 100% !important
}

Update: Lets transform all objects with numeric keys into an array.

const object = {
  "item1": "value",
  "item2/0/subitem1": "value",
  "item2/0/subitem2": "value",
  "item2/1/subitem1": "value",
  "item2/1/subitem2": "value",
  "item3/0/subitem1/subsubitem1": "value",
  "item3/1/subitem1/subsubitem1": "value",
  "item4/0/subitem1/0/subsubitem1": "value",
  "item4/0/subitem1/1/subsubitem1": "value",
}

function flat_directory_list_to_directory_tree(list) {
  let agg = {
    temp: []
  };

  Object.entries(object).forEach(([path, value]) => {
    path.split('/').reduce((agg, part) => {
      if (!agg[part]) {
        agg[part] = {
          temp: []
        };
        agg.temp.push({
          id: part,
          value: value,
          children: agg[part].temp
        })
      }
      return agg[part];
    }, agg)
  })

  var mid = agg.temp;
  // console.log(mid)

  function arr_tree_to_obj(arr) {
    var result = arr.reduce(function(agg, item) {
      if (!item.children.length) {
        agg[item.id] = item.value
      } else {
        agg[item.id] = arr_tree_to_obj(item.children);
      }
      return agg;
    }, {})
    return result;
  }

  var mid2 = (arr_tree_to_obj(mid))
  // console.log(mid2)
  
  function convert_possibly_object_to_array(obj) {
    if (typeof obj !== "object") {
      return obj;
    }
    if (Array.isArray(obj)) {
      return obj;
    }
    var keys = Object.keys(obj);
    if (keys.every(function(key) {
        return Number.isInteger(+key)
      })) {
      return Object.values(obj)
    }
    return obj;
  }


  function iterate2(obj) {
    Object.keys(obj).forEach(function(key) {
      var value = obj[key];
      obj[key] = convert_possibly_object_to_array(value)
      if (typeof value == "object" && value !== null) {
        iterate2(value)
      }
    })
  }

  iterate2(mid2)
  return mid2;
}

var parsedObject = flat_directory_list_to_directory_tree(object);
console.log(parsedObject)
.as-console-wrapper {
  max-height: 100% !important
}

To summarize what this functions does is gets a list of paths or directories and converts it to a trees of three types.

First, makes it a tree with of nodes of type : {id, value, children}

Then converts it to an object whose properties have values or are objects of themselves. This is the classic representation of a tree as a nested object.

Finally, it convert that object and corrects that every object with numeric keys shall become an array. Which is the desired output hopefully

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

3 Comments

I like your answer, but it transforms everything into key-value pairs, but what I need is that when the key after the current key is a number i.e 'item2/0' to make item2 an array and the carry on with the rest i.e. insert {subitem1: 'value'} as an entry in that array
I see. So if to build on top of this solution, every object with numeric keys should be transformed to an array.
Yeah, that's correct

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.