简体   繁体   中英

Build a tree array (3 dimensional) from a flat array in Typescript/Javascript

I have incoming data in this format:

const worldMap = [
  {
    "name": "Germany",
    "parentId": null,
    "type": "Country",
    "value": "country:unique:key:1234",
    "id": "1",
  },
  {
    "name": "North Rhine",
    "parentId": "1",
    "type": "State",
    "value": "state:unique:key:1234",
    "id": "2",
  },
  {
    "name": "Berlin",
    "parentId": "1",
    "type": "State",
    "value": "state:unique:key:1234",
    "id": "3",
  },  
  {
    "name": "Dusseldorf",
    "parentId": "2",
    "type": "city",
    "value": "city:unique:key:1234",
    "id": "4",
  },
   {
    "name": "India",
    "parentId": null,
    "type": "Country",
    "value": "country:unique:key:1234",
    "id": "5",
  }, 
];

I want the output to be something like this:

[
   {
   label: "Germany",
   value: "country:unique:key:1234",
   subs: [
    {
        label: "North Rhine",
        value: "state:unique:key:1234",
        subs: [
            {
                label: "Dusseldorf",
                value: "city:unique:key:1234",
            }
        ]
    },
    {
       label: "Berlin",
       value: "state:unique:key:1234",
    }
   ]
   }
   ,
   {
       "label": "India",
       "value": "country:unique:key:1234"
   }
]

Basically, it is a three dimensional array with first level being the Countrie, second States and third Cities. I have tried the following code:

let tempCountries = [];

worldMap.map((world) => {
  if (world.parentId == null && world.type == "Country") {
    tempCountries.push({label: world.name, value: world.value, id: world.id});
  }
});


tempCountries.map((tempCountry) => {
  const states = worldMap.find((x) => x.parentId == tempCountry.id);
  console.log("=== states ===", states);
  if (states !== undefined) {
      tempCountries.find((x)=>x.id == tempCountry.id).children.push(states)
  }
});

But the above code works upto second level and does not add cities to states. Could anyone please help me achieve this ?

Thanks a lot!

It looks a bit messy, but seems to work.

Basic idea is to generate a maps of ids to world object values. Starting with the base case "country" is easy. The next case is "state" which uses the parentId field to match to the country key. This is also pretty straight forward. The third tier "city" was tricky since the city objects only reference their parent state object id, so a search through each country object's subs object for the matching parentId was necessary.

Once the world array is fully processed into the world mapping object it needs to be reduced back down to just the arrays of values.

One restriction is the assumption made that within the world array that all parent objects would be defined before any nested children. IE A city's state would already have been seen and processed, and a state's country would already have been seen and processed.

const buildWorld = (worldArray) => {
  const builtObj = worldArray.reduce((worldObj, obj) => {
    switch (obj.type) {
      case "Country":
        return {
          ...worldObj,
          [obj.id]: {
            label: obj.name,
            value: obj.value
          }
        };

      case "State":
        return {
          ...worldObj,
          [obj.parentId]: {
            ...worldObj[obj.parentId],
            subs: {
              ...worldObj[obj.parentId].subs,
              [obj.id]: {
                label: obj.name,
                value: obj.value
              }
            }
          }
        };

      case "city":
        const [countryId] = Object.entries(worldObj).find(([key, country]) =>
         country.subs[obj.parentId]
        );
        return {
          ...worldObj,
          [countryId]: {
            ...worldObj[countryId],
            subs: {
              ...worldObj[countryId].subs,
              [obj.parentId]: {
                ...worldObj[countryId].subs[obj.parentId],
                subs: [
                  ...worldObj[countryId].subs[obj.parentId]?.subs ?? [],
                  {
                    label: obj.name,
                    value: obj.value,
                  }
                ]
              }
            }
          }
        };

      default:
        return worldObj;
    }
  }, {});

  

  return Object.values(builtObj).map(country => ({
    ...country,
    ...country.subs ? {subs: Object.values(country.subs)} : {}
  }));
};

 const worldMap = [ { name: "Germany", parentId: null, type: "Country", value: "country:unique:key:1234", id: "1" }, { name: "North Rhine", parentId: "1", type: "State", value: "state:unique:key:1234", id: "2" }, { name: "Berlin", parentId: "1", type: "State", value: "state:unique:key:1234", id: "3" }, { name: "Dusseldorf", parentId: "2", type: "city", value: "city:unique:key:1234", id: "4" }, { name: "India", parentId: null, type: "Country", value: "country:unique:key:1234", id: "5" } ]; const buildWorld = (worldArray) => { const builtObj = worldArray.reduce((worldObj, obj) => { switch (obj.type) { case "Country": return { ...worldObj, [obj.id]: { label: obj.name, value: obj.value } }; case "State": return { ...worldObj, [obj.parentId]: { ...worldObj[obj.parentId], subs: { ...worldObj[obj.parentId].subs, [obj.id]: { label: obj.name, value: obj.value } } } }; case "city": const [countryId] = Object.entries(worldObj).find(([key, country]) => country.subs[obj.parentId] ); return { ...worldObj, [countryId]: { ...worldObj[countryId], subs: { ...worldObj[countryId].subs, [obj.parentId]: { ...worldObj[countryId].subs[obj.parentId], subs: [ ...worldObj[countryId].subs[obj.parentId]?.subs ?? [], { label: obj.name, value: obj.value, } ] } } } }; default: return worldObj; } }, {}); return Object.values(builtObj).map(country => ({ ...country, ...country.subs ? {subs: Object.values(country.subs)} : {} })); }; console.log(buildWorld(worldMap));

You can use a recursive solution:

function convertToTree(layer, parentId = null) {
    const vertex = new Map(), others = [];

    layer.forEach(item => {
        if (item.parentId === parentId) {
            vertex.set(item.id, { label: item.name, value: item.value });
        } else {
            others.push(item);
        }
    });

    for (const vertexId of vertex.keys()) {
        const subs = convertToTree(others, vertexId);
        if (subs.length) {
            vertex.get(vertexId).subs = subs;
        }
    }

    return [...vertex.values()];
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM