One application for a Javascript Proxy object is to reduce network traffic by sending data over the wire as an array of arrays, along with an object listing the field names and index of each field (ie. a field map). (instead of an array of objects where the property names are repeated in each object).
At first glance, it would seem that an ES6 Proxy would be a great way to consume the data on the client side (ie. with the array as the target, and a handler based on the field map).
Unfortunately, Javascript Proxy's have a concept of "invariants", and one of them is that:
[[GetPrototypeOf]], applied to the proxy object must return the same value as [[GetPrototypeOf]] applied to the proxy object’s target object.
In other words, it is not possible to make an array appear as an object (because the prototype of array isn't the same as the prototype of an Object).
A workaround is to make the Object containing the field/index mapping the target, and embed the values in the Proxy handler. This works, but feels dirty. It is basically exactly the opposite of what the Proxy documentation presents and instead of using one "handler" with lots of "targets" it is essentially using lots of "handlers" (each in a closure around the array of values the proxy is representing) all sharing the same "target" (which is the field/index map).
'use strict';
class Inflator {
constructor(fields, values) {
// typically there are additional things in the `set` trap for databinding, persisting, etc.
const handler = {
get: (fields, prop) => values[(fields[prop] || {}).index],
set: (fields, prop, value) => value === (values[fields[prop].index] = value),
};
return new Proxy(fields, handler);
}
}
// this is what the server sends
const rawData = {
fields: {
col1: {index: 0}, // value is an object because there is typically additional metadata about the field
col2: {index: 1},
col3: {index: 2},
},
rows: [
['r1c1', 'r1c2', 'r1c3'],
['r2c1', 'r2c2', 'r2c3'],
],
};
// should be pretty cheap (memory and time) to loop through and wrap each value in a proxy
const data = rawData.rows.map( (row) => new Inflator(rawData.fields, row) );
// confirm we get what we want
console.assert(data[0].col1 === 'r1c1');
console.assert(data[1].col3 === 'r2c3');
console.log(data[0]); // this output is useless (except in Stack Overflow code snippet console, where it seems to work)
console.log(Object.assign({}, data[0])); // this output is useful, but annoying to have to jump through this hoop
for (const prop in data[0]) { // confirm looping through fields works properly
console.log(prop);
}
So:
- Since it is obviously possible to make an array appear to be an object (by holding the array of values in the handler instead of the target); why is this "invariant" restriction applicable in the first place? The whole point of Proxys are to make something look like something else.
and
- Is there a better/more idiomatic way to make an array appear as an object than what is described above?