# json-dry

> Don't repeat yourself, JSON: Add support for (circular) references, class instances, ...

Latest version **2.0.3** (published 2024-08-12) · MIT license · 0 weekly downloads

## Install

```sh
npm install json-dry
pnpm add json-dry
yarn add json-dry
bun add json-dry
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 2.0.3 |
| Published | 2024-08-12 |
| First published | 2014-01-15 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Node | >=14.0 |
| Dependencies | 0 |
| Unpacked size | 49.9 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 230 |
| Author | Jelle De Loecker |
| Maintainers | skerit |
| Keywords | json, circular, serialization, deserialization |

## Links

- npm: https://www.npmjs.com/package/json-dry
- Repository: https://github.com/11ways/json-dry
- Homepage: https://github.com/11ways/json-dry#readme
- Issues: https://github.com/11ways/json-dry/issues
- npm.io page: https://npm.io/package/json-dry

## Alternatives

- [@mapbox/jsonlint-lines-primitives](https://npm.io/package/@mapbox/jsonlint-lines-primitives.md) — 5.3M weekly downloads
- [reftools](https://npm.io/package/reftools.md) — 3.5M weekly downloads
- [@hey-api/openapi-ts](https://npm.io/package/@hey-api/openapi-ts.md) — 3.5M weekly downloads
- [@mapbox/geojson-rewind](https://npm.io/package/@mapbox/geojson-rewind.md) — 2.4M weekly downloads
- [turbo-stream](https://npm.io/package/turbo-stream.md) — 1.7M weekly downloads

## Recent versions

- 2.0.3 (latest) — 2024-08-12
- 2.0.2 — 2023-10-05
- 2.0.1 — 2023-01-23
- 2.0.0 — 2023-01-14
- 1.1.1 — 2022-08-25
- 1.1.0 — 2020-11-12
- 1.0.12 — 2019-11-22
- 1.0.11 — 2019-11-22
- 1.0.10 — 2019-01-31
- 1.0.9 — 2018-12-06
- 1.0.8 — 2018-11-09
- 1.0.7 — 2018-07-11
- 1.0.6 — 2018-07-11
- 1.0.5 — 2018-07-03
- 1.0.4 — 2018-06-18
- … 11 more at https://npm.io/package/json-dry/versions

## README

<h1 align="center">
  <b>JSON-DRY</b>
</h1>
<div align="center">
  <!-- CI - Github Actions -->
  <a href="https://github.com/11ways/json-dry/actions/workflows/unit_test.yaml">
    <img src="https://github.com/11ways/json-dry/actions/workflows/unit_test.yaml/badge.svg" alt="Node.js CI (Linux, MacOS, Windows)" />
  </a>

  <!-- Coverage - Codecov -->
  <a href="https://codecov.io/gh/11ways/json-dry">
    <img src="https://img.shields.io/codecov/c/github/11ways/json-dry/master.svg" alt="Codecov Coverage report" />
  </a>

  <!-- DM - Snyk -->
  <a href="https://snyk.io/test/github/11ways/json-dry?targetFile=package.json">
    <img src="https://snyk.io/test/github/11ways/json-dry/badge.svg?targetFile=package.json" alt="Known Vulnerabilities" />
  </a>
</div>

<div align="center">
  <!-- Version - npm -->
  <a href="https://www.npmjs.com/package/json-dry">
    <img src="https://img.shields.io/npm/v/json-dry.svg" alt="Latest version on npm" />
  </a>

  <!-- License - MIT -->
  <a href="https://github.com/11ways/json-dry#license">
    <img src="https://img.shields.io/github/license/11ways/json-dry.svg" alt="Project license" />
  </a>
</div>
<br>
<div align="center">
  Serialize objects while preserving references and custom class instances
</div>
<div align="center">
  <sub>
    Coded with ❤️ by <a href="#authors">Eleven Ways</a>.
  </sub>
</div>

## Table of contents

  * [Installation](#installation)
  * [Usage](#usage)
    * [Basic example](#basic-example)
    * [Implementing methods for serializing & reviving instances](#implementing-methods-for-serializing--reviving-instances)
    * [toObject](#toobject)
  * [Cloning objects & instances](#cloning-objects--instances)
    * [Clone methods](#clone-methods)
      * [dryClone](#dryclone)
      * [Custom clone methods](#custom-clone-methods)
  * [Project history](#project-history)
  * [Versioning](#versioning)
  * [License](#license)
  * [Acknowledgments](#acknowledgments)

## Version 2.x!

First of all: **Version 2.x of `json-dry` is not able to parse output from version 1.x, if that output contains references!**
The way references are made & revived has changed completely.

All other syntax has remained the same.

If you did not use `json-dry` to store serialized objects long-term (so just on-the-fly, for communication) then it's probably safe to upgrade.

## Installation

    $ npm install json-dry


## Usage

### Basic example

This is a basic example of stringifying an object (containing multiple references to the same object) and parsing it again.

```js
let Dry = require('json-dry');

// The object we'll serialize later
let obj = {};

// The object we'll make multiple references to
let ref = {
    date  : new Date(),
    regex : /test/i
};

// Now we'll make multiple references:
// `reference_one` and `reference_two` both point to the same object
// `date` refers to a `Date` object
obj.reference_one = ref;
obj.reference_two = ref;
obj.date = ref.date;

// Stringify the object
let dried = Dry.stringify(obj);
// {
//     "~refs": [
//         {
//             "date": {"~r": 1},
//             "regex": {
//                 "dry": "regexp",
//                 "value": "/test/i"
//             }
//         },
//         {
//             "dry": "date",
//             "value": "2023-01-14T12:00:35.194Z"
//         }
//     ],
//     "~root": {
//         "reference_one": {"~r": 0},
//         "reference_two": {"~r": 0},
//         "date": {"~r": 1}
//     }
// }

// Now we'll revive it again
let undried = Dry.parse(dried);
// { reference_one: { date: 2018-01-14T17:56:43.149Z, regex: /test/i },
//   reference_two: { date: 2018-01-14T17:56:43.149Z, regex: /test/i },
//   date: 2018-01-14T17:58:50.427Z }

// See if they're the same objects (as it should)
undried.reference_one == undried.reference_two;
// true

// The date outside of the reference object is also the same reference
undried.reference_one.date == undried.date;
// true
```


### Implementing methods for serializing & reviving instances

Let's create an example class you might want to serialize and revive:

```js
// The class constructor
function Person(options) {
    this.firstname = options.firstname;
    this.lastname = options.lastname;
}

// A simple method that prints out the full name
Person.prototype.fullname = function fullname() {
    return this.firstname + ' ' + this.lastname;
};

// Create an object
let jelle = new Person({firstname: 'Jelle', lastname: 'De Loecker'});

// Test out the fullname method
jelle.fullname();
// returns "Jelle De Loecker"
```

So now we've created a very basic class, let's register the class and add the **2** required methods for serializing & reviving.

```js
// We need to register the class
Dry.registerClass(Person);

// Add the `toDry` method that will be called upon when serializing/stringifying
Person.prototype.toDry = function toDry() {
    return {
        value: {
            firstname : this.firstname,
            lastname  : this.lastname
        }
    };
};

// Now add the `unDry` method as a **static** method, on the constructor
Person.unDry = function unDry(value) {
    // How you do this is up to you.
    // You can call the constructor for this simple class,
    // or you can use Object.create, ...
    var result = new Person(value);
    return result;
};
```

Now let's try stringifying it:

```js
let dried = Dry.stringify(jelle);
// {"value":{"firstname":"Jelle","lastname":"De Loecker"},"dry_class":"Person","dry":"toDry","drypath":[]}

// And parse it again
let undried = Dry.parse(dried);
// Person { firstname: 'Jelle', lastname: 'De Loecker' }

// And it works
undried.fullname();
// returns "Jelle De Loecker"
```

## Serializing & reviving instances with circular references

Some classes contain references to each other, for example:

```js
let alpha = new Alpha(),
    beta = new Beta();

alpha.beta = beta;
beta.alpha = alpha;
```

The problem is that when you serialize & then try to revive this, one of the `unDry` methods will receive an un-revived placeholder. This can obviously cause issues, especially when setting the property has side-effects. So a new argument `whenDone` has been added to the `unDry` method, like so:

```js
Alpha.prototype.unDry = function unDry(obj, custom_method, whenDone) {

  let alpha = new Alpha();

  whenDone(function() {
    alpha.beta = obj.beta;
  });

  return alpha;
}
```

`whenDone` functions will be called just before the `Dry.undry()` function exits, so all the references will have been revived by then.

### toObject

While `Dry.stringify` will return you with a json-valid string, `Dry.toObject` will give you a valid simplified object.

In fact: `Dry.stringify` is just a function that performs `JON.stringify` on `Dry.toObject`'s output.

**Why would you want to use this?** Things like `Workers` and `IndexedDB` communicate data using the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). So instead of performing expensive stringify operations you can just use these objects.


## Cloning objects & instances

JSON-Dry offers a specialized `clone` method. While in theory you could clone an object by drying end reviving it, like so:

```js
let cloned = Dry.parse(Dry.toObject(jelle))
```

This is a lot slower than using `clone`, because `toObject` needs to do extra work that can be ignored when cloning:

```js
let cloned = Dry.clone(jelle);
```


### Clone methods

If you've added a `toDry` and `unDry` method to your class, by default the `clone` method will use those to create the clone.

However, you can also create another method that gets precedence:


#### dryClone

```js
Person.prototype.dryClone = function dryClone(seen_map, custom_method) {
    return new Person({
        firstname : this.firstname,
        lastname  : this.lastname
    });
}    
```


#### Custom clone methods

The `clone` method takes an extra parameter called `custom_method`. If you're cloning something that has a function property with the same name, that'll be used.

This can be used when you want to redact certain parts, for example:

```js
Person.prototype.specialOccasionClone = function specialOccasionClone(seen_map, custom_method) {
    return new Person({
        firstname : this.firstname[0] + '.', // Only add the first letter of the name
        lastname  : this.lastname
    });
};

let special_clone = Dry.clone(jelle, 'specialOccasionClone');
special_clone.fullname();
// Returns "J. De Loecker"
```

## Project history

Earlier versions of the project were heavily based on [circular-json](https://github.com/WebReflection/circular-json), a small library that adds (circular) reference support to JSON.

A lot of the JavaScript code on my websites was already shared between the server & client side, but I also wanted an easy way of sending data to the client while retaining references & class instances, so I started adding features to circular-json and called it `json-dry` (*dry* as in *don't repeat yourself*).

The versions of `json-dry` before `2.0.0` used references to the path where the object was first seen, like `~paths~to~the~first~reference`. Unfortunately sometimes objects were nested so deep that these reference paths were a lot longer than the serialized version of the object itself.

That's why in this new version, objects that are used more than once are stored in the `~refs` array. This way all references to objects can be simple numbers, instead of paths.

## Versioning

We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/skerit/json-dry/releases).


## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details

---
_Source: https://npm.io/package/json-dry · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
