Home > Mobile >  JavaScript create a JSON Object from nested flat keys
JavaScript create a JSON Object from nested flat keys

Time:09-08

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

  • Related