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 🙏

© 2026 – Pkg Stats / Ryan Hefner

json-freeze

v1.0.0

Published

RFC 8785 Canonical JSON for JavaScript.

Downloads

42,716

Readme

json-freeze

RFC 8785 Canonical JSON for JavaScript.

JSON.stringify({ name: 'Alice', age: 30 })
// '{"name":"Alice","age":30}'

JSON.stringify({ age: 30, name: 'Alice' })
// '{"age":30,"name":"Alice"}'

// Same data. Different bytes. Any hash, signature, or content
// address built on top of JSON.stringify silently breaks.
import { canonicalize } from 'json-freeze'

canonicalize({ name: 'Alice', age: 30 })
// '{"age":30,"name":"Alice"}'

canonicalize({ age: 30, name: 'Alice' })
// '{"age":30,"name":"Alice"}'

// Same data. Same bytes. That's what RFC 8785 guarantees.

json-freeze implements RFC 8785 (JSON Canonicalization Scheme), the IETF standard for canonical JSON. It's written in pure TypeScript, has no runtime dependencies, and runs unchanged in Node, Bun, Deno and the browsers.

Features

  • Full RFC 8785 compliance
  • String or Uint8Array output for hashing and signing
  • Typed errors with code and path
  • JSON.stringify-compatible replacer for BigInt, Date, and custom types
  • CLI for canonicalizing JSON from the shell

Installation

npm install json-freeze
# or
pnpm add json-freeze
# or
yarn add json-freeze

Quick start

Hash a payload in Node

A canonical hash is the cheapest content identifier you can compute. Two processes on different machines that hold the same logical payload produce the same hash, no matter how each one serialized it.

import { canonicalizeBytes } from 'json-freeze'
import { createHash } from 'node:crypto'

function contentId(value: unknown): string {
  return createHash('sha256').update(canonicalizeBytes(value)).digest('hex')
}

const orderId = contentId({
  userId: 'acct_42',
  amount: 1500,
  currency: 'USD',
  lineItems: [
    { sku: 'book', quantity: 1 },
    { sku: 'mug', quantity: 2 },
  ],
})

Sign and verify in the browser

The signer and the verifier typically run in different processes, often on different machines. Each side calls canonicalizeBytes independently, so the input to the signing and verifying algorithms is byte identical no matter how each side received the payload.

import { canonicalizeBytes } from 'json-freeze'

// Runs on the producer, which holds the private key.
async function sign(payload: unknown, privateKey: CryptoKey) {
  return crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, privateKey, canonicalizeBytes(payload))
}

// Runs on the verifier, which holds the matching public key.
async function verify(payload: unknown, signature: ArrayBuffer, publicKey: CryptoKey) {
  return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, publicKey, signature, canonicalizeBytes(payload))
}

Canonicalize from the shell

The json-freeze CLI reads JSON from stdin or a file and prints the canonical form to stdout.

echo '{"b":2,"a":1}' | json-freeze
# {"a":1,"b":2}

json-freeze payload.json
# {"amount":1500,"currency":"USD"}

API reference

canonicalize(value, options?): string

Returns the RFC 8785 canonical string for value. Throws CanonicalizeError on any value that cannot be canonicalized, such as BigInt, NaN, or a circular reference.

canonicalizeBytes(value, options?): Uint8Array

Returns the UTF-8 byte sequence of the canonical string. Equivalent to new TextEncoder().encode(canonicalize(value, options)) but avoids the manual encoding step at the call site. Same throwing contract as canonicalize.

Options

type Options = {
  replacer?: (this: unknown, key: string, value: unknown) => unknown
}

The replacer matches the second argument to JSON.stringify. It runs after any toJSON method on the value. Return a transformed value to substitute it, or return undefined to drop an object key or emit null in an array position.

canonicalize(
  { id: 9007199254740993n },
  {
    replacer(_key, value) {
      if (typeof value === 'bigint') {
        return value.toString()
      }
      return value
    },
  }
)
// {"id":"9007199254740993"}

CanonicalizeError

class CanonicalizeError extends Error {
  readonly name: 'CanonicalizeError'
  readonly code: CanonicalizeErrorCode
  readonly path: readonly string[]
}

path is the route from the root to the failing value. Each segment is a property name or a stringified array index. An empty array means the root itself.

| code | Triggered by | | -------------------- | ---------------------------------------------------------------------------- | | UNSUPPORTED_TYPE | BigInt, Symbol, Function, or a root value that resolves to undefined | | NON_FINITE_NUMBER | NaN, Infinity, -Infinity | | CIRCULAR_REFERENCE | An object or array that references one of its ancestors | | DUPLICATE_KEY | A replacer produced two entries with the same key |

JsonValue

type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }

Recursive union covering every value the library can serialize without a replacer. Use it as a parameter type when you want the compiler to reject non spec inputs at the call site.

CLI

Usage: json-freeze [file]

Reads JSON from stdin or the given file, writes the RFC 8785 canonical form
to stdout, and exits. A trailing newline is appended for shell friendliness.

Options:
  -h, --help    show this help text

Exit codes:
  0  success
  1  input, file, or JSON parse error
  2  canonicalization error (unsupported type, non finite number, cycle)

License

json-freeze is open-source software licensed under the MIT License.