1

I have the following Object in JS:

var obj = {
  elem_1: "el1",
  elem_2: "el2",
  elem_3: "el3",
  exp_1: "ex1",
  exp_2: "ex2",
  exp_3: "ex3"
};

I am trying to reorder it so each elem_# is followed by exp_#.

I started the following code but not sure how I can achieve it:

obj.sort(function (index, data) { 
    return data.key.split('_')[1] == '1' ? 2 : data.key == '2' ? 1 : 0 
});

How can I achieve what I am looking to do.

Si8
  • 9,141
  • 22
  • 109
  • 221
  • You would have to remove the properties and readd them to the object to preserve the order. If order matters, use a structure that supports it, like an array. – Heretic Monkey Jan 11 '19 at 18:17
  • Unfortunately I can only use an object. I tried converting an array to object everything went out of place. – Si8 Jan 11 '19 at 18:18
  • You can't sort an object, use an array of objects, maybe – ibrahim mahrir Jan 11 '19 at 18:19
  • Keep this in mind: https://stackoverflow.com/questions/5525795/does-javascript-guarantee-object-property-order – wlh Jan 11 '19 at 18:20
  • 1
    https://stackoverflow.com/questions/30076219/does-es6-introduce-a-well-defined-order-of-enumeration-for-object-properties – wlh Jan 11 '19 at 18:31

4 Answers4

3

You can get the keys by doing Object.getOwnPropertyNames(), split() them, and then .sort() them.

I then use .reduce() to turn them back into an object. Reduce iterates through all values of an array to produce a single output. In this case, the output is acc (short for "accumulator") - with each iteration, I add our property to acc by taking the existing contents (...acc) and combining it with our new property ([item]: obj[item]). The ...acc syntax is making use of spread syntax for object literals. The second parameter (just {} in the example below) is the initial value of acc.

It's important to note that property order in objects has only recently become guaranteed in JavaScript (ES2015+, non-integer property names only). Personally, I still consider relying on their order to be a slight smell.

var obj = {
  elem_1: "el1",
  elem_2: "el2",
  elem_3: "el3",
  exp_1: "ex1",
  exp_2: "ex2",
  exp_3: "ex3"
};

var res = Object.getOwnPropertyNames(obj)
  .sort((a,b) => a.split("_")[1] - b.split("_")[1])
  .reduce((acc,item) => ({...acc, [item]: obj[item]}), {});
  
console.log(res);

EDIT: Per this link (mentioned by wlh in a comment above), if dealing with property order, Object.getOwnPropertyNames() is a better choice than Object.keys().

Tyler Roper
  • 21,445
  • 6
  • 33
  • 56
  • What is `...acc`? – Si8 Jan 11 '19 at 18:45
  • 1
    Good question. I'll add some detail to my answer to explain a bit more in-depth. – Tyler Roper Jan 11 '19 at 18:48
  • 1
    @Si8 this will help regarding `...acc` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax – Code Maniac Jan 11 '19 at 18:49
  • ahhhhhh makes sense now... Thanks for the link and also clarification – Si8 Jan 11 '19 at 18:49
  • 1
    @Si8 I've clarified in my answer :) – Tyler Roper Jan 11 '19 at 18:51
  • also I didn't see the `acc` as the parameter... silly me! Now it makes sense. Thanks – Si8 Jan 11 '19 at 18:53
  • hey I for some reason had to not use `.reduce()` and it worked fine. – Si8 Jan 11 '19 at 19:57
  • In the example above, removing the `reduce()` step would output an array of sorted property names. If that's sufficient output then you can lose the `reduce()` portion - that step is only necessary for converting it back into an object (and including the values). If the `reduce` step *isn't working*, it may be that you're using an older browser that doesn't yet support the object spread syntax. – Tyler Roper Jan 11 '19 at 20:02
  • so I just tested it and it shows up fine collapsed but when i expand it, it goes back to the old way – Si8 Jan 11 '19 at 20:51
  • I'm not sure what you mean by "collapse" and "expand". – Tyler Roper Jan 11 '19 at 20:53
  • Nevermind, ES6 that's why it's not working. only upto ES5 unfortunately... I think this code is working correctly – Si8 Jan 11 '19 at 21:18
1

One way of doing this

Take the keys and sort them and than map back get the objects in desired form.

var obj = {
  elem_1: "el1",
  elem_2: "el2",
  elem_3: "el3",
  exp_1: "ex1",
  exp_2: "ex2",
  exp_3: "ex3"
};

let op = Object.keys(obj)
        .sort((a,b)=> a.split('_')[1] - b.split('_')[1])
        .map(e=> ({[e]: obj[e]}) )

console.log(op)

P.S :- This is assumes that your input will always in the defined form as of your example.

Code Maniac
  • 37,143
  • 5
  • 39
  • 60
  • 1
    Thanks. Yes it will always end if a number. However it will fail let's say the number is in the double digit... I think a `split` as mentioned by Tyler is better example for the failesafe – Si8 Jan 11 '19 at 18:25
0

What about

var obj = {
  elem_1: "el1",
  elem_2: "el2",
  elem_3: "el3",
  exp_1: "ex1",
  exp_2: "ex2",
  exp_3: "ex3"
}

obj = Object.entries(obj).sort((a, b) => a[0].split('_')[1]-b[0].split('_')[1])
.map(([key, val]) => ([key, val]))
.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {})

console.log(obj)
user2314737
  • 27,088
  • 20
  • 102
  • 114
0

You could split the keys and sort by number and string in the wanted order by taking an object for the order. Later rebuild a new object.

var object = { elem_1: "el1", elem_2: "el2", elem_3: "el3", exp_1: "ex1", exp_2: "ex2", exp_3: "ex3" },
    order = { elem: 1, exp: 2 };

object = Object.assign(...Object
    .entries(object)
    .sort(([a], [b]) => {
        var aa = a.split('_'),
            bb = b.split('_');
        return aa[1] - bb[1] || order[aa[0]] - order[bb[0]];
    })
    .map(([k, v]) => ({ [k]: v }))
);

console.log(object);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392