surrial
Version:
Serialize anything. This is surreal!
220 lines (167 loc) • 7.75 kB
Markdown
[](https://travis-ci.org/nicojs/node-surrial)
[](https://dashboard.stryker-mutator.io/reports/github.com/nicojs/node-surrial/master)
# Surrial
Serialize anything. Pretty surreal!
## Install
Install using your favorite package manager:
```bash
npm install surrial
# OR
yarn add surrial
```
## Usage
```javascript
const { serialize, deserialize, surrial } = require('surrial');
class Person {
constructor(name, parent){
this.name = name;
this.parent = parent;
}
}
function identity(thing) { return thing; }
const stringified = serialize({
a: 1,
b: new Date(),
c: /foo/,
d: new Set([1, 2, 3]),
e: new Map([[1, 'one'], [2, 'two']]),
f: Person,
g: identity
});
/* => '{ "a": 1, "b": new Date("2018-02-13T20:27:39.073Z"), "c": /foo/, "d": new Set([1, 2, 3]), "e": new Map([[1, "one"], [2, "two"]]), "f": class Person { constructor(name, parent) { this.name = name; this.parent = parent; } }, "g": function identity(thing) { return thing; } }'
*/
const output = deserialize(stringified)
/* =>
{ a: 1,
b: 2018-02-13T20:32:52.218Z,
c: /foo/,
d: Set { 1, 2, 3 },
e: Map { 1 => 'one', 2 => 'two' },
f: [Function: Person],
h: [Function: identity] }
*/
```
Also supports serializing instances of classes for known classes.
```javascript
const p = new Person('Foo', new Person('Bar', null));
const knownClasses = [Person];
const personString = serialize(p, knownClasses);
// => 'new Person("Foo", new Person("Bar", null))'
const copy = deserialize(p, knownClasses);
// => Person { name: 'Foo', parent: Person { name: 'Bar', parent: null } }
```
An example of the `surrial` tag for template literals:
```js
const decade = [new Date(2010, 1, 1), new Date(2020, 1, 1)];
surrial`new Set(${decade})`;
// => 'new Set([new Date("2010-01-31T23:00:00.000Z"),new Date("2020-01-31T23:00:00.000Z")])'
```
You can customize the output string using the `surrialize()` method (comparable to the [`toJSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON_behavior) method for `JSON.stringify`).
```ts
// A typescript example
class Person implements Surrializable {
public age: number;
constructor(ageInMonths: number) {
this.age = Math.floor(ageInMonths / 12);
}
surrialize() {
return surrial`new Person(${this.age * 12})`;
}
}
const input = new Person(25);
const actual = serialize(input, [Person]);
const output = deserialize(actual, [Person]);
expect(output).instanceOf(Person);
expect(output).deep.eq(input);
```
## Api
TypeScript typings are included in the library.
```javascript
/**
* A surrial template tag, useful for building templates strings while enforcing the values to be serialized using surrial.
* @param templateLiterals The template literals
* @param values The values to be serialized using surrial
*/
export function surrial(templateLiterals: TemplateStringsArray, ...values: unknown[]) {
/**
* Serializes the thing to a javascript string. This is NOT necessarily a JSON string, but will be valid javascript.
* @param thing The thing to be serialized
* @param knownClasses the classes of which instances are serialized as constructor calls (for example "new Person('Henry')").
*/
export function serialize(thing: any, knownClasses: ClassConstructor[] = []): string {
/**
* Deserializes a string into it's javascript equivalent. CAUTION! Evaluates the string in the current javascript engine
* (`eval` or one of its friends). Be sure the `serializedThing` comes from a trusted source!
* @param serializedThing The string to deserialize
* @param knownClasses A list of known classes used to provide as constructor functions
*/
export function deserialize(serializedThing: string, knownClasses: ClassConstructor[] = []): any;
```
## Features
* Serializes all primitive types
* Serializes plain objects as JSON
* Support for build in types: `Date`, `RegExp`, `Map`, `Set` and `Buffer`
* Support for functions and classes using their `toString()`
* Support for instances of classes using `new MyClass()` syntax (see [limitations](#class-instances)).
* Support for deeply nested build in types/class instances
* Has a light footprint (< 200 lines of code).
* Written in typescript (type definition included).
* Deserialize using a `deserialize` convenience method. This uses the `new Function(/*...*/)` (comparable to `eval`) (see [limitations](#deserializing-is-no-security-feature-you-will-get-hacked)).
* Serialize values in a template with a handy `surrial` tagged template literal.
* Allow a custom serialize function using `surrialize`.
## Limitations
Surrial, like any serialization library, has some limitations, but supports my personal use case.
If you need more functionality, don't hesitate to open [an issue](https://github.com/nicojs/node-surrial/issues).
I'm always in for a discussion.
### Circular references
Circular references are not supported.
### Deserializing is no security feature (you will get hacked!)
When you call the `deserialize` method, any string will be interpreted as javascript using the [`new Function(...)` constructor](https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Global_Objects/Function). Keep in mind that any arbitrary code will be executed in the global scope of your current javascript engine! Please **don't use this library** to deserialize strings coming from untrusted sources!
### Class instances
Class instances are serialized using their constructor. Any additional properties are ignored.
```javascript
class Person {
constructor(name){
this.name = name;
}
}
const p = new Person('foo');
p.age = 10; // => ignored
serialize(p, [Person]);
// => 'new Person("foo")'
```
Both the `class` syntax and `prototype` syntax (es5 syntax) are supported here.
When serializing an instance of a class, it is assumed that the constructor parameters are also properties (or attributes) of that class. If not, that parameter will be undefined.
```javascript
class Person {
constructor(n, age){
this.name = n; // => ignored
this.age = age;
}
}
const p = new Person('foo', 42);
serialize(p);
// => 'new Person(undefined, 42)'
```
When serializing a class instance, only classes you specify as `knownClasses` are actually serialized using `new MyClass()`,
by default it would just have a JSON format.
```javascript
class Person { constructor(name) { this.name = name; }}
serialize(new Person('Foo'));
// => { "name": "foo" }
serialize(new Person('Foo'), [Person]);
// => new Person("foo")
```
When deserializing a class instance, you are responsible for providing a class definition (or a class with the same name).
```javascript
class Person { constructor(name) { this.name = name; }}
deserialize('new Person("Foo")');
// => ReferenceError: Person is not defined
deserialize('new Person("Foo")', [Person]);
// => OK: Person { name: 'Foo' }
```
## Acknowledgements
* This library is strongly influenced by [serialize-javascript](https://www.npmjs.com/package/serialize-javascript).
This might be what you're looking for when you don't need the class instance serialization support.
* A library which supports circular references: [circular-json](https://www.npmjs.com/package/circular-json)
* Know the class that you're serializing to? [serialize.ts](https://www.npmjs.com/package/serializer.ts) might be for you. This one also looks good: [cerialize](https://www.npmjs.com/package/cerialize)