Style-dictionary: Keys with leading "0" char are not ordered correctly in numeric order

Created on 30 Jan 2019  路  3Comments  路  Source: amzn/style-dictionary

given the following prop file :

{
  "color": {
    "grey": {
      "000": {
        "value": "#FFFFFF"
      },
      "100": {
        "value": "#EEEEF0"
      },
      "200": {
        "value": "#D3D2D6"
      },
    },
  }
}

will output in this order :

  • color-grey-100
  • color-grey-200
  • color-grey-000

this is not a problem when using flattened variables/tokens, but is not optimal for JSON, scss maps or javascript objects where order is important (when iterating through).

Most helpful comment

Javascript is terrible about the order of keys in an object.

The behavior is explicitly left undefined by the ECMAScript specification. In ECMA-262, section 12.6.4 (for-in):

The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified.

and later, in 15.2.3.14 (Object.keys):

If an implementation defines a specific order of enumeration for the for-in statement, that same enumeration order must be used in step 5 of this algorithm.

and worse, in 15.12.2 (JSON.parse), when describing how to parse JSON into an Object:

The ordering of the Strings should be the same as that used by the Object.keys standard built-in function.

Thus, consistency is enforced per-implementation, but not across implementations. Most browsers respect definition order. Chrome and Opera do as well, but only for non-numerical property names. In these two browsers, the properties are pulled by numerical property keys frist, then in definition order (this is has to do with how they implement arrays). This was listed as a bug in chrome at one point, but they said it was working as intended.

Style Dictionary is built on top of Node, and Node is built from V8 (the Chrome JS engine), so it has the same ordering implementation (and problems for you).

It is possible that we could define our own sort order. However, there are 3 big problems in doing this:

  1. Unless we write our own JSON parser, we can't know the initial order of the properties provided by the author.
  2. By defining our own ordering, we move away from existing ordering implementations
  3. We would potentially be changing the output of other people's Style Dictionaries.

The specific behavior in your example is due to one 'gotcha': any key consisting of a string of numbers with a leading 0 is treated as a string and not as a numerical key. Any string of numbers without a leading 0 is treated as a numerical. Thus the string "000" is treated as non-numerical and the strings "100" and "200" are treated as numerical. "0" is numerical, but "00" is not.

You can ensure consistent behavior by forcing the keys to be treated as all strings or all numericals. This will give you the behavior you are looking for.

All numericals:

  • change the keys to "0", "1", and "2"
  • change the keys to "100", "200", and "300"

All strings:

  • change the keys to "0000", "0100", and "0200"
  • change the keys to "sat100", "sat200", and "sat300"

You can play with how your browser of choice implements key order behavior with for-in here (use Chrome to see the behavior you will get with Style Dictionary):
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

I hope this helps explain why you see this behavior, and why defining a more "correct" behavior would be close to impossible.

All 3 comments

I knew this would come up eventually... Going from a tree structure to a flat array can sometimes produce weird behaviors. I don't know if there is a way to enforce the order at the property file level. Here is the function that flattens all the property tree structure:
https://github.com/amzn/style-dictionary/blob/master/lib/utils/flattenProperties.js

If you needed a specific order in an output file, you could write a custom format and in the format you can sort the properties. A quick and dirty example:

const StyleDictionary = require('style-dictionary');
StyleDictionary.registerFormat({
  name: 'mySortedFormat',
  formatter: (dictionary, config) => {
    // .concat() so we don't sort the array in place
    const sortedProperties = dictionary.allProperties.concat().sort((a,b) => {
      // sorting function
    });
    return sortedProperties.map((prop) => {
      // map propers to string
    })
    .join('\n');
  }
});
//...

Javascript is terrible about the order of keys in an object.

The behavior is explicitly left undefined by the ECMAScript specification. In ECMA-262, section 12.6.4 (for-in):

The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified.

and later, in 15.2.3.14 (Object.keys):

If an implementation defines a specific order of enumeration for the for-in statement, that same enumeration order must be used in step 5 of this algorithm.

and worse, in 15.12.2 (JSON.parse), when describing how to parse JSON into an Object:

The ordering of the Strings should be the same as that used by the Object.keys standard built-in function.

Thus, consistency is enforced per-implementation, but not across implementations. Most browsers respect definition order. Chrome and Opera do as well, but only for non-numerical property names. In these two browsers, the properties are pulled by numerical property keys frist, then in definition order (this is has to do with how they implement arrays). This was listed as a bug in chrome at one point, but they said it was working as intended.

Style Dictionary is built on top of Node, and Node is built from V8 (the Chrome JS engine), so it has the same ordering implementation (and problems for you).

It is possible that we could define our own sort order. However, there are 3 big problems in doing this:

  1. Unless we write our own JSON parser, we can't know the initial order of the properties provided by the author.
  2. By defining our own ordering, we move away from existing ordering implementations
  3. We would potentially be changing the output of other people's Style Dictionaries.

The specific behavior in your example is due to one 'gotcha': any key consisting of a string of numbers with a leading 0 is treated as a string and not as a numerical key. Any string of numbers without a leading 0 is treated as a numerical. Thus the string "000" is treated as non-numerical and the strings "100" and "200" are treated as numerical. "0" is numerical, but "00" is not.

You can ensure consistent behavior by forcing the keys to be treated as all strings or all numericals. This will give you the behavior you are looking for.

All numericals:

  • change the keys to "0", "1", and "2"
  • change the keys to "100", "200", and "300"

All strings:

  • change the keys to "0000", "0100", and "0200"
  • change the keys to "sat100", "sat200", and "sat300"

You can play with how your browser of choice implements key order behavior with for-in here (use Chrome to see the behavior you will get with Style Dictionary):
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

I hope this helps explain why you see this behavior, and why defining a more "correct" behavior would be close to impossible.

Closing, feel free to re-open if you have additional questions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

chemoish picture chemoish  路  3Comments

valiksb picture valiksb  路  4Comments

nategreen picture nategreen  路  5Comments

custa1200 picture custa1200  路  5Comments

clepore picture clepore  路  5Comments