1

i have prepared a json tree from a plain json. But i need to sort the tree with multiple conditions. for example at level 1 we have multiple objects. we need to sort with level and then with a name property.

level is a number and name is an alphanumeric. so name sorting is alphabets first and then numbers

Below is the input json

var inputJson = [
  {
  "level": "1",
  "leafFlag": "1",
  "path":"p123",
  "name":"food23"
},
  {
  "level": "1",
  "leafFlag": "1",
  "path":"r125",
  "name":"car1"
},
  {
  "level": "2",
  "leafFlag": "0",
  "path":"p123/p345",
  "name":"apple345"
},
 {
  "level": "2",
  "leafFlag": "1",
  "path":"p123/p095",
  "name":"123banana"
},
{
  "level": "3",
  "leafFlag": "0",
  "path":"p123/p095/p546",
  "name":"543"
},
{
  "level": "2",
  "leafFlag": "1",
  "path":"r125/yhes",
  "name":"tata78"
}
]

var output = [];

The below code prepares the json tree.

I tried here for sorting with multiple properties

inputJson = inputJson.sort((a, b) => (parseInt(a.level) > parseInt(b.level)) ? 1 : -1)
inputJson.forEach(v => {
    if (v.level == "1") {
    v.children = [];
    output.push(v);
  }
  else {
    pathValues = v.path.split("/");
    pathValues.pop();
    var node = null;
    var fullPath = "";
    pathValues.forEach(p => {
        fullPath = fullPath === "" ? p : fullPath + "/" + p;
        node = (node == null ? output : node.children).find(o => o.path === fullPath);
    })
    node.children = node.children || [];
    node.children.push(v);
  }
})

Output from above:

var output = [
  {
    "level": "1",
    "leafFlag": "1",
    "path": "p123",
    "name": "food23",
    "children": [
      {
        "level": "2",
        "leafFlag": "0",
        "path": "p123/p345",
        "name": "apple"
      },
      {
        "level": "2",
        "leafFlag": "1",
        "path": "p123/p095",
        "name": "banana",
        "children": [
          {
            "level": "3",
            "leafFlag": "0",
            "path": "p123/p095/p546",
            "name": "grapes"
          }
        ]
      }
    ]
  },
  {
    "level": "1",
    "leafFlag": "1",
    "path": "r125",
    "name": "car",
    "children": [
      {
        "level": "2",
        "leafFlag": "1",
        "path": "r125/yhes",
        "name": "tata",
        "children": [
          {
            "level": "3",
            "leafFlag": "0",
            "path": "r125/yhes/sdie",
            "name": "Range Rover"
          }
        ]
      },
      {
        "level": "2",
        "leafFlag": "0",
        "path": "r125/theys",
        "name": "suzuki"
      }
    ]
  }
]

Expected output:

[
  {
    "level": "1",
    "leafFlag": "1",
    "path": "r125",
    "name": "car",
    "children": [
      {
        "level": "2",
        "leafFlag": "0",
        "path": "r125/theys",
        "name": "suzuki"
      },
      {
        "level": "2",
        "leafFlag": "1",
        "path": "r125/yhes",
        "name": "tata",
        "children": [
          {
            "level": "3",
            "leafFlag": "0",
            "path": "r125/yhes/sdie",
            "name": "Range Rover"
          }
        ]
      }
    ]
  },
  {
    "level": "1",
    "leafFlag": "1",
    "path": "p123",
    "name": "food",
    "children": [
      {
        "level": "2",
        "leafFlag": "0",
        "path": "p123/p345",
        "name": "apple"
      },
      {
        "level": "2",
        "leafFlag": "1",
        "path": "p123/p095",
        "name": "banana",
        "children": [
          {
            "level": "3",
            "leafFlag": "0",
            "path": "p123/p095/p546",
            "name": "grapes"
          }
        ]
      }
    ]
  }
]

I tried something like below

inputJson = inputJson.sort((a, b) => (parseInt(a.level) > parseInt(b.level)) ? 1 : -1 && a.name > b.name ? 1 ? -1)
AkRoy
  • 343
  • 4
  • 10
  • level is a number and name is an alphanumeric. so name sorting is alphabets first and then numbers – AkRoy Jun 04 '20 at 08:28

3 Answers3

2

You could take a single sort by sorting levels first and then by name.

.sort((a, b) => a.level - b.level || a.name.localeCompare(b.name))

Then build the tree with the sorted items.

var data = [{ level: "1", leafFlag: "1", path: "p123", name: "food" }, { level: "1", leafFlag: "1", path: "r125", name: "car" }, { level: "2", leafFlag: "0", path: "p123/p345", name: "apple" }, { level: "2", leafFlag: "1", path: "p123/p095", name: "banana" }, { level: "3", leafFlag: "0", path: "p123/p095/p546", name: "grapes" }, { level: "2", leafFlag: "1", path: "r125/yhes", name: "tata" }],
    result = data
        .sort((a, b) => a.level - b.level || a.name.localeCompare(b.name))
        .reduce((r, o) => {
            let p = o.path.split('/');
            p.pop();

            let target = p.reduce((t, _, i, p) => {
                var path = p.slice(0, i + 1).join('/'),
                    temp = (t.children = t.children || []).find(q => q.path === path);

                if (!temp) t.children.push(temp = { path }); // this is not necessary
                                                             // if all nodes are given
                return temp;
            }, { children: r });

            (target.children = target.children || []).push({ ...o });
            return r;
        }, []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0
 var rootes= inputJson.filter(x=>x.level=='1') 
           for(i=0;i<rootes.length;i++){
     rootes[i].children=[] }  
     var  objwithchild = inputJson.filter(x=>x.leafFlag=='1') 
           for(i=0;i<objwithchild.length;i++){
            objwithchild[i].children=[] }  

          inputJson.forEach(x=>{
            patharr=x.path.split('/')
            path=patharr.pop()
              switch (x.level) {
                  case '2':
              rootes.filter(p=>{if(p.path==patharr[0]){p.children.push(x)}
               }) 
               break

                case '3':
                    objwithchild.filter(p=>{if(p.path==patharr[0]+'/'+patharr[1]){p.children.push(x)}
                 }) 
                 break
              }
          })

          console.dir(rootes,{depth:null})
Sven.hig
  • 4,449
  • 2
  • 8
  • 18
-2

You should first sort by name, then re-sort the sorted array by level.

inputJson = inputJson.sort((a,b) => {return a.name > b.name}).sort((a,b) => {return (Number(a.level) - Number(b.level)};
Wais Kamal
  • 5,858
  • 2
  • 17
  • 36
  • Sorting is not guaranteed to be stable, therefore sorting twice is not going to necessarily produce the same result as sorting on two criteria as the first order might be lost. – VLAZ Jun 04 '20 at 08:20
  • level is a number and name is an alphanumeric. so name sorting is alphabets first and then numbers – AkRoy Jun 04 '20 at 08:21
  • @VLAZ after sorting by name, you get an array of objects sorted by name. When you re-sort that sorted array by level, you get an array sorted by level first, then by name. – Wais Kamal Jun 04 '20 at 19:14
  • Please also note that when two objects with the same sorting criteria (level in your case) are encountered, their order won't be changed, as stated in [this part](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Description) of the documentation – Wais Kamal Jun 04 '20 at 19:17
  • @WaisKamal from the docs "*If `compareFunction(a, b)` returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. **Note: the ECMAscript standard does not guarantee this behavior***" (emphasis mine). As I said the sorting **is not stable**. Thus it's *valid* for the algorithm to swap two items with the same level, so `[{name: "b", level: 1}, {name: "a", level: 1}]` can be sorted by name first correctly, but swapped around to the initial order when sorting by level - this is standard compliant behaviour. – VLAZ Jun 04 '20 at 21:06
  • Moreover, upon review your sorting of names only returns a boolean [which is bad and it leads to incorrect sorting](https://stackoverflow.com/questions/24080785/sorting-in-javascript-shouldnt-returning-a-boolean-be-enough-for-a-comparison). The very link you provided explicitly says that the comparison function should produce (at least) *three numeric values*, not a boolean. The result should also be consistent when being called with the same arguments but in different order, which a boolean comparison will fail at, consider `a` and `b` being equal to `"apple"` - the result should be `0` – VLAZ Jun 04 '20 at 21:08
  • @VLAZ you said it: "leave a and b unchanged with respect to each other". Now if a is ahead of b, but after sorting the array b comes ahead of a, how is their order unchanged with respect to each other? And regarding the name sort, I assumed no two objects would have the same name, but that is a valid point too. – Wais Kamal Jun 05 '20 at 12:50
  • @WaisKamal I also **bolded** the part immediately following "*Note: the ECMAscript standard does not guarantee this behavior*". As for having the same name - focusing on that specific case is ***irrelevant*** a `>` (or `<`) would treat certain **in**equalities as **equalities**. The first that came to mind was an actual equality. You can also think of `"Bob" > "Alice"` - it is `false` which is then treated as `0` which means the sorting algorithm can treat them as equal and thus decide to sort *both* after `"Banana"` for example. The order `"Banana", "Bob", "Alice"` is incorrect. – VLAZ Jun 05 '20 at 14:07