Elastic Object
Plain Objects are great for handling data, but they can be a bit clunky. This is where Elastic Object comes in. On top of the regular object functionality, it features get()
and set()
with dotted.string.notation
, as well as loops and all the other good stuff you already know from Arrays, Maps or Sets. It is easily extendable in case you want to add your own methods. And it's not huge either.
Installation
npm i elastic-object
Usage
Below is a simple example of how to create an Elastic Object. For the methods, please refer to the complete documentation.
import ElasticObject from 'elastic-object'; // note that ElasticObject is implemented as ESM and not in CJS
const eObj = new ElasticObject({
path: {
to: {
string: "foo",
integer: 42
},
},
another: {
path: {
to: {
float: 3.14
}
}
}
});
console.log(eObj.get('path.to.string')); // "foo"
eObj.set('path.to.string', "bar");
console.log(eObj.get('path.to.string')); // "bar"
eObj.path.to.string = "quux";
console.log(eObj.get('path.to.string')); // "quux"
eObj.forEach((value, key) => {
console.log(key, value); // "path", { to: { string: "bar", integer: 42 } } etc.
});
Features
Standard object methods
Elastic Objects are extensions of plain objects, so everything you can do with plain objects can be done with elastic objects, too. There are some differences, though:
- Static methods, such as
assign()
orcreate()
, which you would expect to return regular objects, will return elastic objects instead. keys()
,values()
,entries()
,assign()
,create()
andfromEntries()
are also available as instance methods. From within the instancekeys()
,values()
,entries()
refer tothis
and take no arguments.assign()
usesthis
as thetarget
argument.create()
andfromEntries()
work exactly like their static counterparts.
Accessors
Accessing properties with set('path.to.property')
is a common implementation pattern, but it's not native to JavaScript. With set()
, get()
, has()
and unset()
Elastic Object has built-in support for this pattern. To avoid confusion with JavaScript's native dot notation, this document uses the term dotted.string.notation
instead. The feature is powered by Sindre Sorhus' dot-prop library.
Array methods
Not all array methods make sense on an object, but every()
, filter()
, find()
, forEach()
, includes()
, length()
, map()
, reduce()
, reduceRight()
, some()
and sort()
certainly do; Elastic Object has them all. There is also findPath()
as an equivalent of findIndex()
. All methods work pretty much like their array counterparts, which means they generally refer to the top level of the object. findPath()
, however, searches all levels as long as the values are either arrays or objects. Note that length()
is, contrary to the array implementation, built out as a function.
Other methods
toJson()
, clone()
and cloneProperty()
cover common tasks and it makes sense to have them available. The flatten()
method finally returns a version of the object with all nested paths converted to strings in dotted.string.notation
. The above example as a flat object looks like this:
{
'path': {to: {…}}
'path.to': {string: 'string', integer: 42, float: 3.14, boolean: true, null: null}
'path.to.boolean': true
'path.to.float': 3.14
'path.to.integer': 42
'path.to.null': null
'path.to.string': "string"
}
Chainability
Whenever a method would normally return an object, an elastic object with the same plugins will be returned instead. This means that you can chain methods, like this:
eObj.set('path.to.string', "foo").set('path.to.integer', 42).set('another.path.to.float', 3.14);
eObj.get('path.to.a.plain.object').filter(value => value > 3).values();
Adding properties
You aren't limited to Elastic Object's native functionality. It has a plugin system that allows you to pass an object of new methods as an argument to the constructor. /plugins/array.js
and /plugins/native.js
are loaded by default and you can check out these modules if you wish to add your own set of methods. Methods are added to the prototype chain, which may make them available project-wide depending on your implementation . Make sure that they implement the chainability paradigm as mentioned above.
Below is a short example of how this works. Keep in mind to use regular function syntax to ensure access to this
.
const myPlugins = {
methodA: function() {
console.log(this);
}
// more methods
}
const data = { bar: 42 };
// standard Elastic Object
const eObj1 = new ElasticObject(data);
eObj1.methodA(); // TypeError: eObj1.methodA is not a function
// Elastic Object with custom methods
const eObj2 = new ElasticObject(data, myPlugins);
eObj2.methodA(); // ElasticObject { bar: 42 }
// Load plugins after creation
const eObj3 = ElasticObject.create(data);
eObj3.methodA(); // TypeError: eObj1.methodA is not a function
eObj3.loadPlugins(myPlugins); // now it works
eObj3.methodA(); // ElasticObject { bar: 42 }
// the magic formula for chainability:
import isPlainObject from "whats-the-type/isPlainObject.js";
const foo = function() {
const value = {a: 1}; // or whereever your value comes from
return isPlainObject(value) ? this.create(value) : value;
}
Method overview
The following table is an overview of the available methods. The links in the first column point to this documentation, the second column to the original documentation which you might also find helpful.
Method | External reference | Notes |
---|---|---|
eObj.assign() |
Object.assign() |
Instance flavor: eObj.assign(...sources) , this is the target |
ElasticObject.assign() |
Object.assign() |
Static flavor: ElasticObject.assign(target, ...sources) |
eObj.create() |
Object.create() |
Instance flavor: eObj.create(anyObj) |
ElasticObject.create() |
Object.create() |
Static flavor: ElasticObject.create(anyObj) |
eObj.clone() |
stucturedClone() |
Clones the whole object |
eObj.cloneEntry() |
stucturedClone() |
Clones any property of the object |
eObj.entries() |
Object.entries() |
Instance flavor: eObj.entries() , uses this |
ElasticObject.entries() |
Object.entries() |
Static flavor: ElasticObject.entries(anyObj) |
eObj.every() |
Array.every() |
Same signature and usage |
eObj.filter() |
Array.filter() |
Returns an Elastic Object, chain .values() to get only the values |
eObj.find() |
Array.find() |
Same signature and usage |
eObj.findPath() |
Array.findIndex() |
Returns path in dotted.string.notation rather than an index |
eObj.flatten() |
Flattened version of the object | |
eObj.forEach() |
Array.forEach() |
Same signature and usage |
eObj.fromEntries() |
Object.fromEntries() |
Instance flavor: eObj.fromEntries(iteratable) |
ElasticObject.fromEntries() |
Object.fromEntries() |
Static flavor: ElasticObject.fromEntries(iteratable) |
eObj.get() |
getProperty() |
Same signature, but without the first argument object |
eObj.has() |
hasProperty() |
Same signature, but without the first argument object |
eObj.includes() |
Array.includes() |
Same signature and usage |
eObj.keys() |
Object.keys() |
Instance flavor: eObj.keys() , uses this |
ElasticObject.keys() |
Object.keys() |
Static flavor: ElasticObject.keys(anyObj) |
eObj.length() |
Array.length |
In difference to Array.length , this is implemented as a function |
eObj.map() |
Array.map() |
Same signature and usage |
eObj.paths() |
Keys of the flattened object | |
eObj.reduce() |
Array.reduce() |
Same signature and usage |
eObj.reduceRight() |
Array.reduceRight() |
Same signature and usage |
eObj.set() |
setProperty() |
Same signature, but without the first argument object |
eObj.some() |
Array.some() |
Same signature and usage |
eObj.sort() |
Array.sort() |
Same signature and usage |
eObj.toJson() |
JSON.stringify() |
With boolean pretty argument for pretty-print |
eObj.unset() |
deleteProperty() |
Same signature, but without the first argument object |
eObj.values() |
Object.values() |
Instance flavor: eObj.values() , uses this |
ElasticObject.values() |
Object.values() |
Static flavor: ElasticObject.values(anyObj) |
Resources
- Repository: github.com/draber/elastic-object
- Package: npmjs.com/package/elastic-object
- Docs: elastic-object.netlify.app