Modern, TypeScript-first, ESM-only Rison: compact serialization for JSON-like structures — great for URLs and for compact storage. Based on Rison (Original).
Rison is a slight variation of JSON that looks vastly superior after URI encoding — ideal for
compact state in URLs and also useful for storing structured data in localStorage
/sessionStorage
.
- Encodes nested objects/arrays compactly and URI‑friendly (minimal escaping).
- Ideal for client state in query or fragment (filters, sorting, columns, pagination).
- Practical URL limits are infrastructure‑driven: conservative target ≈ 2 KB total; common defaults ≈ 8 KB. Fragments (
#state=…
) aren’t sent to servers and bypass those limits. - This package includes native compression helpers (gzip/deflate via CompressionStream) with an
auto
mode that selects the shortest token.
import { encode, decode } from "@effective/rison";
encode({ any: "json", yes: true });
// -> (any:json,yes:!t)
decode("(any:json,yes:!t)");
// -> { any: 'json', yes: true }
Optional URL compression helpers (native CompressionStream in modern browsers):
import { compressToUrl, decompressFromUrl } from "@effective/rison";
// mode: 'auto' | 'deflate' | 'none' (default: 'auto')
const compact = await compressToUrl({ page: 1, filters: { active: true } }, { mode: 'auto' });
// -> safe, compact string for query params/fragments
const value = await decompressFromUrl(compact);
### Compression formats & prefixes
- `none`: returns raw Rison (no prefix)
- `deflate`: returns a token prefixed with `d:` and base64url‑encoded (deflate‑raw)
- `auto`: compares raw Rison vs deflate and picks the shortest
// -> original value
For storage, URL compatibility isn’t required. This library offers high‑density UTF‑16 packing (Base32768‑like) that maps 15‑bit chunks to non‑surrogate code points, giving better density than base64. Storage helpers default to base32768
and keep the same compression prefixes.
import { compressForStorage, decompressFromStorage } from "@effective/rison";
// mode: 'auto' | 'deflate' | 'none' (default: 'auto')
// encoding: 'base32768' | 'base64' (default: 'base32768')
const token = await compressForStorage({ theme: 'dark', tabs: [1, 2, 3] }, { encoding: 'base32768' });
// store in localStorage yourself
localStorage.setItem('app:state', token);
// later…
const restored = await decompressFromStorage(localStorage.getItem('app:state')!, { encoding: 'base32768' });
Convenience wrappers are also available if you prefer direct storage calls:
import { saveToLocalStorage, loadFromLocalStorage } from "@effective/rison";
await saveToLocalStorage("app:state", { theme: "dark" }, { encoding: "base32768" });
const state = await loadFromLocalStorage("app:state", { encoding: "base32768" });
pnpm add @effective/rison
# or
npm install @effective/rison
- Browsers: modern engines support CompressionStream/DecompressionStream.
- Node.js: 20+ provides CompressionStream in runtime; this repo builds/tests on Node 22+.
- ESM-only package.
encode(value)
→ stringdecode(string)
→ anyencodeUri(value)
→ string (Rison-encode + relaxed URI escaping)compressToUrl(value, { mode })
→ PromisedecompressFromUrl(string)
→ PromisecompressForStorage(value, { mode, encoding })
→ PromisedecompressFromStorage(string, { encoding })
→ PromisesaveToLocalStorage(key, value, { mode, encoding })
→ PromiseloadFromLocalStorage(key, { encoding })
→ Promise<any | null>
Notes:
- Storage encoding defaults to
base32768
for maximum density with simple decoding. You can switch tobase64
for interoperability. - Compression prefixes remain the same:
d:
(deflate-raw) or none (raw rison).
Types are published via dist/rison.d.ts
.
- Live demo: 3‑pane source → converted → restored
- Radio options for compression: auto, deflate, none
- Three presets (Short/Medium/Long) to test compression impact
Use Rison with nuqs to manage URL state in React apps.
import { useQueryState } from "nuqs";
import { encode, decode } from "@effective/rison";
// Rison serializer for nuqs
const risonSerializer = {
parse: (value: string) => decode(value),
serialize: (value: unknown) => encode(value)
};
export function Example() {
// e.g. ?state=(filter:(active:!t),page:3)
const [state, setState] = useQueryState("state", risonSerializer as any);
return (
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
<button onClick={() => setState({ filter: { active: true }, page: 3 })}>Set State</button>
</div>
);
}
Compression helpers are async and not suitable for nuqs serializers; if you need compression with nuqs, use a custom async layer outside of serializer calls.
For smaller URLs, use the compressed variant above.
- ESM-only package, Node 22+ recommended.
- Removed default export; use named imports.
- Removed legacy O-/A-Rison helpers; use
encode
/decode
only.
- Run tests:
pnpm test
- Typecheck:
pnpm run typecheck
- Format:
pnpm run format
Manual, assisted by release-it. Ensure your npm and GitHub tokens are configured locally.
pnpm install
pnpm run release
This runs typecheck/tests, builds, bumps the version, publishes to npm (public), tags the release, and publishes a GitHub Release.
MIT — see LICENSE.md.
- Copyright © 2007–2009 Metaweb Technologies, Inc.
- Copyright © 2025–present, Sebastian Software GmbH, Germany.
- Originally created and published by Metaweb Technologies, Inc. (Google).
- Encoder inspired by Douglas Crockford's json.js; decoder inspired by Oliver Steele's JSON for OpenLaszlo.
- Thanks to all contributors over the years.