Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .changeset/add-flatten.md

This file was deleted.

9 changes: 0 additions & 9 deletions .changeset/add-notations.md

This file was deleted.

12 changes: 0 additions & 12 deletions .changeset/add-path-helpers.md

This file was deleted.

13 changes: 0 additions & 13 deletions .changeset/add-unflatten.md

This file was deleted.

46 changes: 44 additions & 2 deletions .changeset/flattify-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,48 @@
'flattify': major
---

flattify 2.0 is a full rewrite in TypeScript: ESM and CommonJS builds, zero dependencies, published on npm and JSR (`@nandomb/flattify`), and tested on Node.js, Bun, Deno, browsers and Cloudflare Workers.
**Why a major version?**

**Breaking:** the default export `flattify(json, isFlattifyArray)` was removed. The migration guide ships with the new `flatten`/`unflatten` API.
Flattify 2.0 is a full rewrite in TypeScript. The single default export `flattify(json, isFlattifyArray)` is replaced by named exports, `flatten` and `unflatten`, with different defaults. Code written for 1.x has to be updated (see Migration below).

**Breaking changes**

- **Named exports.** `flattify(...)` is replaced by `flatten(...)`: `import { flatten } from 'flattify'` or `const { flatten } = require('flattify')`.
- **Arrays are flattened by default.** `flatten({ tags: ['a'] })` gives `{ 'tags.0': 'a' }`. The 1.x default, which turned arrays into JSON strings, is gone: pass `{ safe: true }` to keep arrays as arrays.
- **Top-level arrays are flattened as a whole.** `flatten([{ id: 1 }])` gives `{ '0.id': 1 }` instead of flattening each item: use `array.map((item) => flatten(item))` for the 1.x behavior.
- **JSON strings are no longer parsed.** Pass `JSON.parse(json)` instead of the string; anything but a plain object or an array throws a `TypeError`.
- **Keys that contain the delimiter are escaped** (`{ 'a.b': 1 }` gives `{ 'a\.b': 1 }`), so they come back whole. Pass `{ escape: false }` to leave them as they are.
- **TypeScript 5.0+** for the types.

**Migration**

```diff
- const flattify = require('flattify');
- flattify(data, true);
+ const { flatten } = require('flattify');
+ flatten(data);
```

| 1.x | 2.x |
| --- | --- |
| `flattify(obj, true)` | `flatten(obj)` |
| `flattify(obj)` | `flatten(obj, { safe: true })` |
| `flattify(array)` | `array.map((item) => flatten(item))` |
| `flattify(jsonString)` | `flatten(JSON.parse(jsonString))` |

**Fixed**

- Nested arrays kept their path: `{ a: { tags: [1] }, b: { tags: [2] } }` gave `{ tags: '[2]' }` in 1.x, losing `a.tags`. It now gives `{ 'a.tags.0': 1, 'b.tags.0': 2 }`.
- Calls no longer share state: 1.x stored the array option in a module-level variable.
- Inherited properties are no longer read, and circular references no longer overflow the stack.

**New**

- **`unflatten`**, the reverse of `flatten`: `unflatten(flatten(data))` gives back `data`, including dates, class instances, `null`, empty objects and arrays, and keys with dots in them. `asArray` gives back an array given to `flatten`.
- **Type inference:** every path and value of the result is typed (`Flatten<T>`, `Unflatten<T>`), for every option that changes them.
- **Options:** `delimiter`, `maxDepth`, `safe`, `keepEmpty`, `escape`, `circular`, `transformKey` and `preserve` for `flatten`; `delimiter`, `object`, `overwrite`, `arrayLimit`, `asArray`, `escape` and `transformKey` for `unflatten`.
- **Three notations:** `items.0.id` (dot), `items[0].id` (bracket, and `unflatten` reads form fields such as `items[0][id]`) and `/items/0/id` (JSON Pointer, RFC 6901).
- **`flattify/path`:** `get`, `set`, `has`, `del`, `paths`, `parsePath`, `stringifyPath` and `escapeKey`, with autocompleted paths and type-checked values.
- **Safe on untrusted input:** no prototype pollution through `__proto__`, `constructor` or `prototype`, no stack overflow on deep or circular input, and `arrayLimit` against huge sparse arrays. Covered by tests, including fuzzing.
- **Everywhere:** ESM and CommonJS builds with zero dependencies, published on npm and on JSR as [`@nandomb/flattify`](https://jsr.io/@nandomb/flattify), and tested on Node.js 22, 24 and 26, Bun, Deno, Chromium, Firefox, WebKit and Cloudflare Workers.
- **Trusted releases:** published from CI without long-lived tokens, with npm provenance, and only live after a maintainer approves them with 2FA (staged publishing).
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# [flattify](https://github.com/NandoMB/flattify)
# [Flattify](https://github.com/NandoMB/flattify)

[![npm version](https://img.shields.io/npm/v/flattify.svg)](https://www.npmjs.com/package/flattify)
[![JSR](https://jsr.io/badges/@nandomb/flattify)](https://jsr.io/@nandomb/flattify)
Expand All @@ -17,14 +17,14 @@ Turn nested objects into flat `path → value` pairs **and back, without losing
- **Checked, not claimed:** 100% test coverage, thousands of random round trips per run, and every [comparison](#why-flattify) with other libraries verified in CI
- Zero dependencies, ESM and CommonJS, on [npm](https://www.npmjs.com/package/flattify) and [JSR](https://jsr.io/@nandomb/flattify). Runs on Node.js, Bun, Deno, browsers and Cloudflare Workers

> Upgrading from flattify 1.x? See [Migrating](#migrating).
> Upgrading from Flattify 1.x? See [Migrating](#migrating).

## Contents

- [Installation](#installation)
- [Quick start](#quick-start)
- [Data integrity](#data-integrity)
- [Why flattify?](#why-flattify)
- [Why Flattify?](#why-flattify)
- [Use cases](#use-cases)
- [API](#api): [`flatten`](#flatteninput-options), [`unflatten`](#unflatteninput-options), [notations](#notations), [`flattify/path`](#flattifypath)
- [Types](#types)
Expand Down Expand Up @@ -88,7 +88,7 @@ back.items[0].sku; // string

## Data integrity

Most libraries lose something on the way: a `Date` becomes `{}`, `null` disappears, an empty list vanishes, `'utm.source'` is split in two. flattify keeps all of it:
Most libraries lose something on the way: a `Date` becomes `{}`, `null` disappears, an empty list vanishes, `'utm.source'` is split in two. Flattify keeps all of it:

```ts
const input = {
Expand Down Expand Up @@ -123,11 +123,11 @@ back['utm.source']; // 'newsletter', still one key

Each guarantee is covered by the test suite, and the round trip is checked on thousands of random objects on every run ([fast-check](https://fast-check.dev)), for every notation.

## Why flattify?
## Why Flattify?

Every row is checked by [`bench/claims.spec.ts`](./bench/claims.spec.ts) against flat 6.0.1, flattie 1.1.1, nestie 1.0.3, es-toolkit 1.52.0 and radashi 12.9.6, on every change in CI (`pnpm claims`).

| | flattify | [flat](https://www.npmjs.com/package/flat) | [flattie](https://www.npmjs.com/package/flattie) / [nestie](https://www.npmjs.com/package/nestie) | [es-toolkit](https://www.npmjs.com/package/es-toolkit) | [radashi](https://www.npmjs.com/package/radashi) |
| | Flattify | [flat](https://www.npmjs.com/package/flat) | [flattie](https://www.npmjs.com/package/flattie) / [nestie](https://www.npmjs.com/package/nestie) | [es-toolkit](https://www.npmjs.com/package/es-toolkit) | [radashi](https://www.npmjs.com/package/radashi) |
| --- | --- | --- | --- | --- | --- |
| Keys with the delimiter survive the round trip | ✅ | ❌ | ❌ | – | ❌ |
| Keeps `Date` and `Map` as values | ✅ | ✅ | ❌ dropped | ✅ | ✅ |
Expand All @@ -144,7 +144,7 @@ Every row is checked by [`bench/claims.spec.ts`](./bench/claims.spec.ts) against
| ESM and CommonJS | ✅ | ❌ v6 is ESM only | ✅ | ✅ | ✅ |
| Size (min + gzip) | 2.3 kB | 0.7 kB | 0.35 kB | 0.34 kB | 0.8 kB |

flattify is bigger because it does more: the checks above, three notations, escaping and clear error messages. `flattify/path` is a separate import, so it only counts when you use it.
Flattify is bigger because it does more: the checks above, three notations, escaping and clear error messages. `flattify/path` is a separate import, so it only counts when you use it.

## Use cases

Expand Down Expand Up @@ -308,7 +308,7 @@ CI runs the tests on Node.js 22, 24 and 26, Bun, Deno, Chromium, Firefox, WebKit

Same option names (`delimiter`, `maxDepth`, `safe`, `object`, `overwrite`, `transformKey`), with safer defaults:

| | `flat` | flattify |
| | `flat` | Flattify |
| --- | --- | --- |
| Result type | you pass it: `flatten<T, R>()` | inferred |
| Keys with the delimiter | split in two on the way back | escaped; `{ escape: false }` for the old behavior |
Expand All @@ -318,7 +318,7 @@ Same option names (`delimiter`, `maxDepth`, `safe`, `object`, `overwrite`, `tran
| Class instances | flattened | kept as values |
| Circular references | stack overflow | `TypeError`, or `circular: 'skip'` |

### From flattify 1.x
### From Flattify 1.x

2.0 is a rewrite with named exports:

Expand Down
Loading