jed / lave

eval in reverse: stringifying all the stuff that JSON.stringify won't
MIT License
896 stars 19 forks source link

lave Build Status

lave is eval in reverse; it does for JavaScript what JSON.stringify does for JSON, turning an arbitrary object in memory into the expression, function, or ES6 module needed to create it.

Why not just use JSON.stringify?

JSON is great data transport, but can only handle a subset of the objects expressible in a JavaScript runtime. This usually results in lossy serializations at best, and TypeError: Converting circular structure to JSON at worst. While we can get around such issues by writing JavaScript code to parse this JSON back into the structures we want, now we have to ship that code out of band, which can be a headache.

Instead of writing a parser for a new language that can represent arbitrary JavaScript runtime values, I built lave to use the best language for the job: JavaScript itself. This allows it to handle the following structures that JSON can't.

Type JavaScript JSON.stringify lave
Circular references a={}; a.self=a :x: TypeError :white_check_mark: var a={};a.self=a;a
Repeated references a={}; [a, a] :warning: [{}, {}] :white_check_mark: var a={};[a,a]
Global object global :x: TypeError :white_check_mark: (0,eval)('this') ?
Built-in objects Array.prototype :warning: [] :white_check_mark: Array.prototype
Boxed primitives Object('abc') :warning: "abc" :white_check_mark: Object('abc')
Functions [function(){}] :warning: [null] :white_check_mark: [function(){}]
Dates new Date(1e12) :warning: "2001-09-09T01:46:40.000Z" :white_check_mark: new Date(1000000000000)
NaN NaN :warning: null :white_check_mark: NaN
Infinity Infinity :warning: null :white_check_mark: Infinity
Sets and Maps new Set([1,2,3]) :warning: {} :white_check_mark: new Set([1,2,3])
Sparse arrays a=[]; a[2]=0; a :warning: [null,null,0] :white_check_mark: var a=[];a[2]=0;a
Object properties a=[0,1]; a.b=2; a :warning: [0,1] :white_check_mark: var a=[0,1];a.b=2;a
Custom prototypes Object.create(null) :warning: {} :white_check_mark: Object.create(null)

Keep in mind that there are some things that not even lave can stringify, such as function closures, or built-in native functions.

Example

This command...

node << EOF
  var generate = require('escodegen').generate
  var lave = require('lave')

  var a = [function(){}, new Date, new Buffer('A'), global]
  a.splice(2, 0, a)

  var js = lave(a, {generate, format: 'module'})
  console.log(js)
EOF

...outputs the following JavaScript:

var a = [
    function (){},
    new Date(1456522677247),
    null,
    Buffer('QQ==', 'base64'),
    (0, eval)('this')
];
a[2] = a;
export default a;

When would I want to use this?

How does lave work?

lave uses all of the syntax available in JavaScript to build the most concise representation of an object, such as by preferring literals ([1,2]) over assignment (var a=[];a[0]=1;a[1]=2). Here's how it works:

Installation

Please run the following command to install lave:

$ npm install lave

The library has been tested on Node 4.x and 5.x.

API

ast = lave(object, [options])

By default, lave takes an object and returns an abstract syntax tree (AST) representing the generated JavaScript. Any of the following options can also be specified:

Addenda