8

I know key order isn't guaranteed in JS objects, however, my data structure comes from a backend for which I have no control over. Is there anything I can do to preserve the key order when going from:

var obj = {
   foo: 'bar',
   bar: 'foo' 
};

to:

Object.keys(obj); // hoping for ['foo', 'bar']

It's of the utmost importance that I keep the key order unfortunately...

benhowdle89
  • 36,900
  • 69
  • 202
  • 331
  • 4
    How do you expect to maintain key order when the key order itself is non-deterministic? :S – Dan Jul 14 '15 at 14:13
  • Well property ordering really isn't guaranteed, unfortunately. The order will be whatever it happens to be. Why is it important? – Pointy Jul 14 '15 at 14:13
  • @DanPantry The object is parsed from JSON sent by a Python backend...which maybe does support key order? – benhowdle89 Jul 14 '15 at 14:14
  • 1
    It doesn't matter what Python does. – Pointy Jul 14 '15 at 14:15
  • Once the object crosses the JSON barrier, there is no guarantee on the order of your properties (which makes sense because you cant' really 'order' properties). It might make more sense to put each of your properties into its own object in an array, and send that instead. Arrays preserve key order. Even if you *could* maintain the order of an object when its converted to an array, the original order of the object would be non-deterministic anyway, so you would never be able to define the order of the object anyway. – Dan Jul 14 '15 at 14:16
  • You can parse the »raw String« from the server-side and build up an array based data-structure which will preserve the order… – philipp Jul 14 '15 at 14:18
  • @philipp this is possible. but also brittle. What happens if I add another property in the middle of that object before I serialize it? Everything breaks. In an ideal API, any extra properties would be ignored by older consumers, instead of breaking them. – Dan Jul 14 '15 at 14:18
  • Not with sensible parsing. You could easily break the JSON into arrays of strings and get the relevant data out of the ones you want, regardless of any other "properties" that would be included. If you *need* to keep the order then you must do it this way, as converting to an object probably alphabetises the order anyway. – Reinstate Monica Cellio Jul 14 '15 at 14:19
  • If the keys are known ahead of time you could write a map of sorts. `{'foo':0, 'bar':1}` – nbering Jul 14 '15 at 14:19
  • I wouldn't say that rolling your own JSON parser (and/or using `eval`) is a "sensible" alternative to just turning the representation of your data into something that makes more sense for the task at hand. I'm assuming this is an API, so other people may want to consume it as well. You should make this as easy as possible on them, and on yourself. – Dan Jul 14 '15 at 14:21
  • @DanPantry I didn't say it was sensible. I said that using sensible parsing (thinking outside the box about how it may break in the future) would resolve the problem that the OP presented in the previous comment. – Reinstate Monica Cellio Jul 14 '15 at 14:22
  • @Archer my apologies, then. OP, you might be better off looking at other formats that are sensitive to positioning. For example, protocol buffers (although I am not sure if this works in the browser). – Dan Jul 14 '15 at 14:23
  • @DanPantry no worries. I personally think we need to know *why* the OP wants this, as it sounds like an XY problem to me. – Reinstate Monica Cellio Jul 14 '15 at 14:24
  • Let's look at the OP again: **a backend for which I have no control over** - A custom JSON parser isn't hard, and it doesn't really have to do anything unorthodox. – Pointy Jul 14 '15 at 14:24
  • @Archer Why I want this? I don't want this at all. It's a workaround to cater for the structure I'm sent from the backend. I've requested that an "index" key is added to each object to dictate order, but you know how these things go... – benhowdle89 Jul 14 '15 at 14:26
  • Okay - maybe "want" is the wrong word, but you are here asking for it. What I mean is *why* do you need this? The answer is not because you can't modify the server-side code. The answer is why you need to retain the order for your client-side code. If we knew why this is an issue then maybe there is a more simple solution than rewriting the JSON parser. – Reinstate Monica Cellio Jul 14 '15 at 14:28
  • @Archer Fair point. It's simply to display in the DOM as a list, but in the same order as was sent in the dictionary structure (the key order). – benhowdle89 Jul 14 '15 at 14:30
  • I would suggest you to change the data structure if you care about the key order. Use array of objects instead of hash. – hindmost Jul 14 '15 at 14:30
  • Is it literally a dictionary of `key: value`? Because if it is then simply removing the braces and splitting by a comma will give you what you need. Obviously if it's more complex data then it will need recursion, but it should still not be too hard. Can you give a more real world example of the data, as a string? – Reinstate Monica Cellio Jul 14 '15 at 14:31
  • Java has as `LinkedHashMap`, I guess something a Structure like that could do the job… – philipp Jul 14 '15 at 15:06

4 Answers4

8

No. As you wrote:

I know key order isn't guaranteed in JS objects

If you want the order you need to use an array. If you have an object, then there is no defined order to the properties.

dsh
  • 12,037
  • 3
  • 33
  • 51
  • 1
    This. The only exception would be if you could grab the JSON that the backend sends, before it's actually a Javascript object (i.e. when it's still a string), and parse that string for its keys. By no means ideal of course, but would technically work. – Shai Jul 14 '15 at 14:18
2

The JSON specification indicates that

An object is an unordered collection of zero or more name/value pairs...

Strictly speaking, the literal JSON text has an order, of course: the text isn't going to suddenly scramble itself. But your desired behavior -- an ordered set of name/value pairs -- is not a construct that JSON provides. You want an object notation to provide semantic meaning beyond the semantics that are required by the JSON specification.

In order words, doing this in JSON

{
    "foo": "baz",
    "bar": "egg" 
}

is description of an unordered set. If you intended for this to describe an ordered set, you're not following the rules of JSON. The data happens to be ordered (because that's how character sequences work), but JSON semantics freely allow an implementation to disregard the order of name/value pairs completely when considering the data present in the input string.

You could write code that operates directly on the JSON input string, to parse the text in such a way that the input order of the keys is remembered somewhere, but a JSON parser implementation is not required to supply that functionality to you.

The correct JSON-idiomatic solution would be to provide an array of key names in the JSON response:

{
    data: {
        "foo": "baz",
        "bar": "egg" 
    },
    order: ["foo", "bar"]
}

Obviously, if you don't control the response, you can't do that. By choosing the represent the data in an object, the author of the response has asserted that order of keys is purely arbitrary. If the author of the JSON intends to assert order of key entries, that assertion is external to the rules of JSON.

As a practical matter, most JavaScript engines will usually choose to report key names in the order their properties were created. However, this behavior is not required by any specification (and is sometimes inconsistent in edge cases), and so could legally differ between engines and versions.

apsillers
  • 112,806
  • 17
  • 235
  • 239
  • I feel as though this answer would have received a better response if it were written for the audience. This reads more like the documentation and less like an explanation of it. – Abandoned Cart Apr 16 '19 at 15:22
2

This is doable since ES2015.

Stefan Judis explains it really well here https://www.stefanjudis.com/today-i-learned/property-order-is-predictable-in-javascript-objects-since-es2015/


Here's how it works:

  • Number-like keys always come first, no matter when you inserted them. They are sorted numerically.

  • Strings come next. They are sorted chronologically. Insert "foo" after "bar", and it'll stay that way!

  • Symbols come last. They are sorted chronologically, too.

const obj = {
  '2': 'integer: 2',
  'foo': 'string: foo',
  '01': 'string: 01', // "01" counts as a string. Only "1" is a number-like key!
  1: 'integer: 1',
  [Symbol('first')]: 'symbol: first'
};

The resulting order of those keys is "1", "2", "foo", "01", Symbol(first).

The integers were moved to the start and sorted numerically. Next are strings, in the same order as they were ("foo", then "01"). Symbols are moved to the end, but otherwise keep their order.

merlindru
  • 99
  • 2
  • 8
1

ECMA-262 does not specify enumeration order. The de facto standard is to match insertion order.

No guarantees are given though on the enumeration order for array indices (i.e., a property name that can be parsed as an integer), because insertion order for array indices would incur significant memory overhead.

EDIT: added jsfiddle example.

var obj1 = {
   a: 'test1',
   b: 'test2' 
};

var obj2 = {
   2: 'test1',
   1: 'test2' 
};
// max 32bit unsigned is 2,147,483,647
var obj3 = {
   2147483649: 'test1',
   2147483648: 'test2' 
};
// max 64bit unsigned is 9,223,372,036,854,775,807
var obj4 = {
   9223372036854770: 'test1',
   9223372036854768: 'test2',
};

// != number < 2,147,483,647, order is not changed
console.log(Object.keys(obj1));
// < 2,147,483,647, order is changed
console.log(Object.keys(obj2));
// > 2,147,483,647, order is not changed in Firefox, but changed in Chrome
console.log(Object.keys(obj3));
// > 9,223,372,036,854,775,807, order is not changed in neither Firefox or Chrome
console.log(Object.keys(obj4));

Which means Chrome's javascript engine V8 will order any 64bit unsigned numbers, while Firefox's javascript engine SpiderMonkey will order any 32bit unsigned numbers.

Sotiris Kiritsis
  • 3,178
  • 3
  • 23
  • 31