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.
CodePudding user response:
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