Detalhes do pacote

json-dry

11ways4.4kMIT2.0.3

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

json, circular, serialization, deserialization

readme (leia-me)

JSON-DRY


Serialize objects while preserving references and custom class instances
Coded with ❤️ by Eleven Ways.

Table of contents

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.

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:

// 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.

// 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:

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:

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:

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. 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:

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:

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

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:

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, 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 for versioning. For the versions available, see the tags on this repository.

License

This project is licensed under the MIT License - see the LICENSE file for details

changelog (log de mudanças)

2.0.3 (2024-08-12)

  • Fix placeholders not being replaced in certain circular circumstances

2.0.2 (2023-10-05)

  • Add BigInt support

2.0.1 (2023-01-23)

  • Optimize speed by always setting the root property
  • Optimize speed by removing the class member initializers
  • Optimize the clone methods
  • Store driers & undriers in a map instead of an object

2.0.0 (2023-01-14)

  • Rewrite serialization & parser logic
  • Store any value that is referred to more than once in a separate root property

1.1.1 (2022-08-25)

  • Make Dry.parse() add the current path being revived to possible errors

1.1.0 (2020-11-12)

  • Added whenDone function parameter to the unDry & unDryFunction function calls
  • Fixed infinite loop issue when using Dry.clone() that relies on unDry methods

1.0.12 (2019-11-22)

  • Simple version boost to fix NPM publish issue

1.0.11 (2019-11-22)

  • Split up dryReplacer function, added replaceObject function
  • Don't assign regenerated value if it's exactly the same, prevents HTMLCollection errors
  • Fix throwing an error when serializing an invalid date

1.0.10 (2019-01-31)

  • Add extra constructor checks before drying values with a Date or RegExp class name
  • Fix some recursive issues in the clone() method
  • Fix values not being undried when it's a child of another undried object
  • Fix retrieveFromPath throwing an error when the array contains a number

1.0.9 (2018-12-06)

  • Also export the walk function
  • Use Namespace.getClassForUndry() methods if available

1.0.8 (2018-11-09)

  • Fix reviving nested undried classes
  • The unDry static method will receive a second argument: false if used for regular unserializing or true/custom_method_name if used for cloning.

1.0.7 (2018-07-11)

  • Fix registered undriers without a drypath again by reviving the values immediately

1.0.6 (2018-07-11)

  • Fix registered undriers not being called on dried values without a drypath property

1.0.5 (2018-07-03)

  • Remove dead code
  • Add findClass to the exported functions
  • Enable registering classes with a namespace property

1.0.4 (2018-06-18)

  • Fix circular references that were incorrectly passed to an undrier

1.0.3 (2018-02-06)

  • Fix certain references not being regenerated correctly because key iteration happens out-of-order

1.0.2 (2018-02-06)

  • Fix objects being passed to unDry functions without being fully regenerated

1.0.1 (2018-01-23)

  • Fix reference paths being replaced with paths of equal or even longer size

1.0.0 (2018-01-15)

  • Rewrite code, take over new features from the protoblast package
  • WeakMap is now used instead of multiple Array objects
  • A clone method has been added

0.1.6 (2014-02-26)

  • Register seen objects under their constructor name when stringifying for speed improvements

0.1.5 (2014-02-17)

  • Rewrite the retrieveFromPath function to be more readable and error-safe

0.1.4 (2014-02-03)

  • Fix generation of wrong paths when objects are empty

0.1.3 (2014-02-03)

  • Add support for infinity
  • Fix null object bug

0.1.2 (2014-01-21)

  • Path exporting functions

0.1.0 (2014-01-15)

  • Fork circular-json
  • Initial release