A minimalistic yet efficient way to stringify and revive instances via JSON.

Overview

json-instances

build status Coverage Status CSP strict

Social Media Photo by Francisco J. Villena on Unsplash

A minimalistic yet efficient way to stringify and revive instances via JSON.

If stringified instances have a fromJSON() {} method in their prototypal chain, such method will be invoked once the instance gets revived.

import JSONInstances from 'json-instances';

class MyThing {
  constructor(thing) {
    this.some = thing;
  }
  toString() {
    return this.some;
  }
}

class OtherThing {
  constructor() {
    this.revived = false;
  }
  fromJSON() {
    this.revived = true;
  }
}

// pass along any constructor that should survive JSON serialization
// and remember that order matters, as constructors are indexed!
const {replacer, reviver} = JSONInstances(
  MyThing,
  OtherThing
);

const before = [
  new MyThing('cool!'),
  new OtherThing
];

const str = JSON.stringify(before, replacer);
console.log(str);
// [{"i":0,"o":[["some","cool!"]]},{"i":1,"o":[["revived",false]]}]

const after = JSON.parse(str, reviver);
console.log(after);
// [ MyThing { some: 'cool!' }, OtherThing { revived: true } ]

How does it work

This module provides both a replacer and a reviver callback able to convert every known class passed during initialization, either as array or as namespace.

This means that if the structure you are trying to stringify includes unknown instances that are not plain objects or not known upfront when these callbacks are created, the resulting serialization and deserialization will not work as expected.

Please be sure all classes meant to be revived are passed along and don't be surprised if errors happen when this is not the case.

Hackable

Because constructors are not serialied, just referenced as index of an array, it is possible to use same ordered amount of classes in multiple workers, as well as different client/server classes, as long as the index well represents the purpose of the data/class associated with it.

const client = [
  UIComponent,
  User
];

const user = new User(name);
const comp = new UICOmponent({props: values});

const state = JSON.stringify(
  {user, comp},
  JSONInstances(client).replacer
);

storeState(state);

// server
const backend = [
  SSRComponent,
  UserAuth
];

const {user, comp} = JSON.parse(
  state,
  JSONInstances(backend).reviver
);

user.authenticate();
response.write(comp.toString());

Namespace based

By default this module accepts a list of classes because referring to these as indexes makes the JSON outcome extremely compact.

However, there might be a preference around a namespace able to make the outcome more readable, and this is usable via the json-instances/namespace dedicated export, sharing also 99% of the code with the array based version.

import JSONInstances from 'json-instances/namespace';

class Some {}
class Other {}
class Test {}

// the namespace used to stringify and revive
const nmsp = {
  deeper: {
    Some,
    Other,
  },
  Test
};

const {replacer, reviver} = JSONInstances(nmsp);

const str = JSON.stringify([{}, new Some, new Other, new Test], replacer);
// [{"i":"","o":[]},{"i":"deeper.Some","o":[]},{"i":"deeper.Other","o":[]},{"i":"Test","o":[]}]

const [a, b, c, d] = JSON.parse(str, reviver);

a.constructor === Object; // true
b instanceof Some;        // true
c instanceof Other;       // true
d instanceof Test;        // true
You might also like...

Command line tool to interact with exist-db instances (pre-release)

xst [ĭg-zĭst′] Command line tool to interact with exist-db instances. Built on top of @existdb/node-exist. Installation Until this package is official

Aug 4, 2022

🐞 A NodeJS module to access Bugzilla instances through the REST API.

Bugzilla | Typesafe access to Bugzilla's REST API. Very early work in progress, getting info from a bug or searching bugs is the main priority right n

Nov 1, 2022

Synchronize multiple Pi-hole instances

Orbital Sync Orbital Sync synchronizes multiple Pi-hole instances for high availability (HA) using the built-in "teleporter". In other words, it perfo

Dec 30, 2022

A simple web server exposing Hetzner cloud instances for consumption by the Prometheus HTTP service discovery.

Prometheus: Hetzner Service Discovery A server to provide automatic node discovery for Hetzner Cloud to Prometheus via HTTP service discovery. In cont

Oct 10, 2022

Easy and simple way to share data via mobile’s built-in share module.

React-Mobile-Share Provides an easy and simple way to share data (such as text, url and media) via mobile’s built-in share module. It is based on Web

Dec 28, 2022

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions. (like child-processes, iframe, web worker etc).

Dec 29, 2022

@TGMusicfy - Minimalistic Telegram music search bot written in TypeScript and based on Telegraf and Express JS.

@TGMusicfy Go to bot Deployed thanks to Heroku and New-Relic Bots are special Telegram accounts designed to handle messages automatically. Users can i

Sep 13, 2022

Minimalistic portfolio/photography site with masonry grid, page transitions and big images.

Gatsby Starter Portfolio: Emilia Minimalistic portfolio/photography site with masonry grid, page transitions and big images. Themeable with Theme UI.

May 20, 2022
Comments
  • non-iterable object error

    non-iterable object error

    Andrea, first of all, thank you SO much for putting this together, and so quickly.

    I have been testing with this module and tried a few different approaches / combinations. My experience with this kind of thing is limited, so forgive me for my ignorance.

    calling parse(str, reviver) results in the following error:

    index.js:26 Uncaught (in promise) TypeError: 
    object is not iterable (cannot read property Symbol(Symbol.iterator))
        at fromEntries (<anonymous>)
        at Object.reviver (index.js:26:27)
    

    Multiple combinations result in the same error. My latest implementation is as follows:

    class Reviver {
       constructor() {
          this.revived = false
       }
       fromJSON() {
          this.revived = true
       }
       revived: any
    }
    
    const {replacer, reviver} = JSONInstances(MyClass, Reviver)
          
    const before = [
       new MyClass(arg1, arg2),
       new Revive()
    ]
    
    const str = stringify(before, replacer)
    const after = parse(str, reviver)
    

    I'm sure I'm misunderstanding how to implement it. Would appreciate guidance. Thanks again!

    UPDATE: I tracked the error to the function 'reviver', where you call fromEntries(o). fromEntries expects an array, where as some items that come in are object. As a work around I wrapped this section in an if statement:

    if (Array.isArray($.o)) {
        const {i, o} = $, v = fromEntries(o)
        m.set($, v).set(v, v)
        if (-1 < i && ('fromJSON' in setPrototypeOf(v, c[i].prototype))) {
           v.fromJSON()
        }
    }
    

    Once I did that, your module accomplished exactly what I need. Thank you VERY much!!

    opened by alexshenker 7
Owner
Andrea Giammarchi
Web, Mobile, IoT and all Web & JS things since 00's
Andrea Giammarchi
Argon - extension for VS Code and plugin for Roblox allowing easy two-way sync of code and instances

About Argon is a simple two-way sync plugin for Roblox and extension for Visual Studio Code allowing developers not only to sync code but every possib

DARK 16 Dec 29, 2022
zieeco 12 Jul 8, 2022
JCS (JSON Canonicalization Scheme), JSON digests, and JSON Merkle hashes

JSON Hash This package contains the following JSON utilties for Deno: digest.ts provides cryptographic hash digests of JSON trees. It guarantee that d

Hong Minhee (洪 民憙) 13 Sep 2, 2022
Package fetcher is a bot messenger which gather npm packages by uploading either a json file (package.json) or a picture representing package.json. To continue...

package-fetcher Ce projet contient un boilerplate pour un bot messenger et l'executable Windows ngrok qui va permettre de créer un tunnel https pour c

AILI Fida Aliotti Christino 2 Mar 29, 2022
An efficient (and the fastest!) way to search the web privately using Brave Search Engine

Brave Search An efficient (and the fastest) way to search the web privately using Brave Search Engine. Not affiliated with Brave Search. Tested on Chr

Jishan Shaikh 7 Jun 2, 2022
An efficient drop-in replacement for JSON.

JCOF: JSON-like Compact Object Format A more efficient way to represent JSON-style objects. Status This format isn't nailed down yet. Most changes wil

Martin Dørum 147 Nov 26, 2022
Experience Lab is a set of utilities that assist in creating instances of Microsoft Energy Data Services, performing data loads, and performing basic management operations.

Experience Lab - Microsoft Energy Data Services Build Status About Experience Lab is an automated, end-to-end deployment accelerator for Microsoft Ene

Microsoft 9 Dec 14, 2022
An open-source, pretty, simple and fast meilisearch UI for managing your meilisearch instances

Meilisearch-UI An open-source, pretty, simple and fast meilisearch UI for managing your meilisearch instances [IMPORTANT] The main branch may be unsta

Kyrie Lrvinye 29 Dec 29, 2022
A set of tools, helping you building efficient apps in a fast way. >> SvelteKit & GraphQL <<

KitQL KitQL, A set of tools, helping you building efficient apps in a fast way. ?? Infos Documentation: https://kitql.vercel.app/ Day by day progress,

JYC 262 Dec 27, 2022