1

I have a javascript (node.js) object:

const fileObj = {filename: 'file1', filepath: '/file1/path/', flag: true,}

With objects, I was told there is no guarantee of order (unlike arrays which are explicitly ordered) -- So can I be certain that

Object.values(fileObj)

will spit out the values in the same order as the keys spit out with

Object.keys(fileObj)

The reason this is critical is using mysql escaped values in node:

const mysql = require('mysql');
const connection = mysql.createConnection(credentials); 

const fileObj = {filename: 'file1', filepath: '/file1/path/', flag: true,}
const cols = Object.keys(fileObj).join(', ');
const placeholder = Object.keys(fileObj).fill('?').join(', ');

const sql = `INSERT INTO table1 (${cols}) VALUES (${placeholder})`;

connection.query(sql, Object.values(fileObj), function (error, results, fields) {
    // Do stuff
});

This creates a SQL statement:

sql = INSERT INTO table1 (filename, filepath, flag) VALUES (?, ?, ?);

Which theoretically is then coupled with the escape values:

connection.query(sql, ['file1', '/file1/path/', true], function...

But if they are not all in the same order, game over...


Related questions:

Does JavaScript Guarantee Object Property Order? (Javscript does not guarantee order... except maybe you can expect order...)

Does ES6 introduce a well-defined order of enumeration for object properties? (Order guaranteed sometimes... but not with Object.keys?)

Which is it? And what should I be using to make sure my mySQL escape values are in the same order as my mySQL column names?

Trees4theForest
  • 1,267
  • 2
  • 18
  • 48
  • Relying on property order is a great way to make your code extremely fragile. If you want to impose an order, use an array. – Pointy Feb 09 '18 at 01:24
  • 2
    Use `Object.entries()` – SLaks Feb 09 '18 at 01:25
  • 2
    Use `Object.entries` and map over it twice! – Ry- Feb 09 '18 at 01:25
  • 1
    Or use `Object.entries` per other suggestions and map over it once using the `INSERT... SET...` SQL syntax. – fubar Feb 09 '18 at 01:31
  • 1
    `Object.entries(yourObject)` does not guarantee the _order_ of the entries, but it does keep the keys:values paired. – Stephen P Feb 09 '18 at 01:31
  • @Ryan Ahh... further muddying the waters ;-). So if I'm in ES2018, order IS guaranteed with .keys and .values? (Can you point me to the spec?) – Trees4theForest Feb 09 '18 at 01:31
  • Sorry, that was a misreading of the spec. The order isn’t guaranteed. Edit edited back out. – Ry- Feb 09 '18 at 01:36
  • I see ES2018 spec 19.1.2.16 and 19.1.2.21 (https://tc39.github.io/ecma262/#sec-object.keys) both use EnumerableOwnProperties which according to 7.3.21(5) says: Order the elements of properties so they are in the same relative order as would be produced by the Iterator that would be returned if the EnumerateObjectProperties internal method were invoked with O -- which uses ownpropertykeys which is ordered... ?!?! – Trees4theForest Feb 09 '18 at 01:42
  • 1
    According to a few of the people involved in writing ES specs that language does not guarantee order since in the end all the `if x was invoked as` boil down to an internal method that does not specify order at all – slebetman Feb 09 '18 at 01:53

4 Answers4

3

I would use Object.entries per the suggest of multiple other users.

You can then choose to continue to extract the keys and values, this time with the order guaranteed for use in your SQL statement.

var attributes = {
  a: 'A',
  f: 'F',
  c: 'C',
  b: 'B',
  e: 'E',
  d: 'D',
};

const entries = Object.entries(attributes);
const keys = entries.map(entry => entry[0]);
const values = entries.map(entry => entry[1]);

console.log(keys, values);
fubar
  • 16,918
  • 4
  • 37
  • 43
  • so object.entries IS guaranteed order every time it's called? – Trees4theForest Feb 09 '18 at 01:46
  • 2
    `Object.entries` returns an array of array key/value pairs. So the order doesn't actually matter, because the key and value will always be together. – fubar Feb 09 '18 at 01:55
  • 2
    @fubar, since you call `Object.entries` twice, they may come in different order each time, right? – Kosh Feb 09 '18 at 01:58
  • @KoshVery - Good shout. I've updated my answer to call it once. – fubar Feb 09 '18 at 01:59
  • @Paulpro: No, ES6 introduced an order (that `[[OwnPropertyKeys]]` follows, for example), but `for in` is specifically allowed to ignore that and do something implementation-defined. `Object.keys`/`values`/`entries` all follow the implementation-defined `for in` order. `Reflect.ownKeys` is one thing that uses the ES6 order. – Ry- Feb 09 '18 at 07:22
2

You might convert your object into arrays in a single loop. It will guarantee the correct order:

const fileObj = {filename: 'file1', filepath: '/file1/path/', flag: true,}

const vals = [], placeholder = [];
const keys = Object.keys(fileObj).map(k => vals.push(fileObj[k]) && placeholder.push('?') && k);

console.log(`INSERT INTO table1 (${keys}) VALUES (${placeholder})`);
console.log(keys + ''); console.log(vals + '');
Kosh
  • 16,966
  • 2
  • 19
  • 34
1

If you use the mysql2 npm package, you can use this much better syntax.

const fileObj = {filename: 'file1', filepath: '/file1/path/', flag: true,}
const sql = `INSERT INTO table1 SET ?`;
const result = await connection.query(sql, [fileObj]);
Evert
  • 93,428
  • 18
  • 118
  • 189
0

I'd personally not use any hackish manipulation of objects (the design of which originally is supposed to be unordered map of key to values) because they can be implementation defined (and sometimes vary between versions of the same interpreter).

If you want order use an array:

const fileObj = [
  {
    key: 'filename',
    val: 'file1'
  },
  {
    key: 'filepath',
    val: '/file1/path/'
  },
  {
    key: 'flag',
    val: true,
  }
];

This is unambiguous and works on all versions and implementations of JS.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • True... but if the data comes in un-ordered object form... getting it into an ordered array is the trick. – Trees4theForest Feb 09 '18 at 02:41
  • @Trees4theForest you can get from an object literal to the above structure using a single line of JavaScript: `fileObj = Object.entries(fileObj).map(([key, value]) => ({key, value}));` – fubar Feb 09 '18 at 05:01