npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

json22

v0.0.8

Published

JSON superset with an ability to deal with classes and extended support for number values

Downloads

10

Readme

JSON22 - JSON with types

The JSON22 is a superset of JSON with an ability to serialize/deserialize classes and extended support for number variables.

TL;DR

To there ...

const value = {
    name: "Femistoclus",
    amount: 3873133n,
    debt: NaN,
    date: new Date('2022-01-07'),
};
const string = JSON22.stringify(value);
console.log(string);
// =>
// {
//    "name": "Femistoclus"
//    "amount": 3873133n,
//    "debt": NaN,
//    "date": Date(1641513600000),
// }

... and back

const string = `{
  "name": "Femistoclus"
  "amount": 3873133n,
  "debt": NaN,
  "date": Date(1641513600000),
}`;
const value = JSON22.parse(string);
console.log(typeof value.date, value.date.constructor.name); // => object Date
console.log(typeof value.amount); // => bigint
console.log(typeof value.debt, isNaN(value.debt)); // => number true

Features

  • Can parse standard JSON format
  • Support for BigInt values
  • Support for NaN values
  • Support for Infinity/-Infinity values
  • Support for typed serialization/deserialization, work with Date class out of the box
  • Allow using trailing commas
  • Zero-dependency npm-package
  • Both CJS/ESM modules support

Installation

npm install json22

In your code

import { JSON22 } from 'json22'

const data = { date: new Date() };
const s = JSON22.stringify(data);

For old-fashioned applications

const { JSON22 } = require('json22'); 

const data = { date: new Date() };
const s = JSON22.stringify(data);

Integration

Using with Express

There is library json22-express providing JSON22 support for expressjs applications

import express from 'express'; 
import { json22express } from 'json22-express'

const app = express();
app.use(json22express());

app.get('/date', (req, res, next) => {
    res.status(200).json22({ date: new Date() });
    next();
});

Using with Axios

There is library json22-axios providing JSON22 support for client applications

import axios from 'axios';
import { Json22RequestInterceptor } from 'json22-axios';

axios.interceptors.request.use(Json22RequestInterceptor());

async function geServerDate() {
  try {
      const resp = await axios.get('/date');
      return resp.data.date;
  } catch (e) {
    console.error(e);
  }
  return null;
}

API

Note: JSON22 cannot be used as drop in JSON object replacement due to parse and stringify methods arguments incompatibility. But you may not be worried in case you are using first arguments only.

class JSON22 {
    static readonly mimeType: string;
    static parse<T>(text: string, options?: Json22ParseOptions): T;
    static stringify(value: any, options?: Json22StringifyOptions): string;
}

interface Json22ParseOptions {
    context?: Record<string, { new (...args: any) }>; // default { 'Date': Date }
    // To be extended
}

interface Json22StringifyOptions {
    // To be extended
}

JSON Extensions

Numbers

With JSON22 you can use NaN, Infinity, -Infinity values. It means also this values will be stringified well in case it nested at an array or an object.

JSON.stringify([42, NaN, Infinity, -Infinity]); // => [42, null, null, null] 
JSON22.stringify([42, NaN, Infinity, -Infinity]); // => [42, NaN, Infinity, -Infinity]
JSON.stringify({ nan: NaN }); // => { "nan": null } 
JSON22.stringify({ nan: NaN }); // => { "nan": NaN }

BigInt

JSON22 introduce support for BigInt values

JSON.stringify({ bigint: 123n }); // => Uncaught TypeError: Do not know how to serialize a BigInt
JSON22.stringify({ bigint: 123n }); // => { "bigint": 123n } 
JSON22.parse('{ "bigint": 123n }'); // => { bigint: 123n }

Trailing commas

It was not planned, but parser implementation work well with trailing commas. There is no sense to complicate the parser code to avoid it. It looks useful.

JSON.parse('[1, 2, 3, ]'); // => Uncaught SyntaxError: Unexpected token ] in JSON at position 9
JSON22.parse('[1, 2, 3, ]'); // => [1, 2, 3]
JSON.parse('{ "a": 1, "b": 2, "c": 3, }'); // => Uncaught SyntaxError: Unexpected token } in JSON at position 26
JSON22.parse('{ "a": 1, "b": 2, "c": 3, }'); // => { a: 1, b:2, c:3 }

Typed values

This is the most significant addition. It's allow you to serialize and deserialize any typed value. Out of the box it works well with date values.

const date = new Date('2022-01-07');
JSON.stringify(date); // => "2022-01-07T00:00:00.000Z"
JSON22.stringify(date); // => Date(1641513600000)
const date = JSON22.parse('Date(1641513600000)');
console.log(typeof date, date instanceof Date); // => object true 

This behavior is based on the valueOf method which is defined at the Object class. In case JSON22 find the valueOf method return a value which is not equal of the object itself then it will produce constructor literal. The valueOf of the Date class return numeric date representation. If you'll call the Date constructor with that value then date will be sort of 'restored'.

Custom valueOf implementation

To match this behavior you may implement you own valueOf method at you custom class.

Let's define a model class for demonstration

class TypedModel {
    constructor(data) {
        this.a = data?.a;
        this.b = data?.b;
    }
    valueOf() {
        return { a: this.a, b: this.b };
    }
}

That sort of classes will be serialised as typed objects

const value = new TypedModel({ a: 1, b: 2 });
JSON22.stringify(value); // => TypedModel({ "a": 1, "b": 1 }) 

The valueOf methods may return any serializable values, even typed objects

const value = new TypedModel({ a: 1, b: new Date('2022-01-07') });
JSON22.stringify(value); // => TypedModel({ "a": 1, "b": Date(1641513600000) }) 

Parsing context

Typically, serialization and deserialization are processes separated by different environments. Like serialization at a backend and deserialization at a frontend and vice versa. So TypedModel we defined above should be shared between environments. Also JSON22 parser should have a link to this class. In theory, we can push all such classes to a global scope. It is easy, however, it is not the best solution. It will produce global scope pollution, may cause naming conflicts, and it is not safe to allow parser to call any constructor from a global scope. That is why you should always pass deserialization context to parser.

const string = 'TypedModel({ "a": 1, "b": Date(1641513600000) })';
JSON22.parse(string); // => Error: Constructor TypedModel not defined in the context

const context = { 'TypedModel': TypedModel };
const value = JSON22.parse(string, { context });
console.log(value instanceof TypedModel); // => true

The valueOf method priority

The JSON22 support for toJSON method of an object as well as JSON. In some cases an object may have both valueOf and toJSON methods. Typical example is the Date class. The JSON22 at first is a solution to serialize/deserialize date values, so valueOf have higher priority over toJSON. This is also true for any object implementing valueOf and toJSON both.

Motivation

JSON format is good enough for everyday usage. There are some libraries trying to introduce syntax to make JSON closer to modern JavaScript, some libraries trying to introduce functions serialization. All that is not important and is not required for everyday usage. However, there is one thing annoying me always - date values.

We are serializing dates a lot and each time we parse it back we are getting a string. As a result we have to deal with the Date constructor manually each time. Even if we are no need date as an object, date formatter will have to make date object in order to make user-friendly text representation. Otherwords we are forced to care about dates additionally. It produces bulky solutions or tons of inline type conversions.

But I'm lazy developer, I'll do everything to get rid of any additional careness.