Getting started
bash
npm install @flattype/core @flattype/models @flattype/httpts
import { encode, decode } from "@flattype/core";
encode({ name: "John", age: 30, city: "New York" });
// name: John
// age: 30
// city: New York
decode("name: John\nage: 30"); // { name: "John", age: 30 }No bundler? One script tag puts everything on a FlatType global:
html
<script src="https://cdn.jsdelivr.net/npm/@flattype/core@1"></script>
<script>
FlatType.encode({ name: "John" }); // "name: John"
</script>Lists, the shape of most API responses, become tables. Keys are written once, and nested objects become dot paths:
ts
encode({
page: 1,
users: [
{ id: 1, name: "Alice", address: { city: "Paris" } },
{ id: 2, name: "Bob", address: { city: "Delhi" } },
],
});
// page: 1
// users[2]{id,name,address.city}:
// 1, Alice, Paris
// 2, Bob, DelhiObjects that don't all have the same keys stay compact too: a key some lack becomes an optional field (phone?), and a list that can't be a table is written item by item, with [+] starting each item and . lines continuing it:
ts
encode({
users: [{ id: 1, name: "Alice", phone: "+1-555-0100" }, { id: 2, name: "Bob" }],
events: [{ type: "push", commits: [{ sha: "a1b2" }] }, { type: "star", user: { login: "zoe" } }],
});
// users[2]{id,name,phone?}:
// 1, Alice, +1-555-0100
// 2, Bob,
// events[+].type: push
// .commits[1]{sha}:
// a1b2
// events[+].type: star
// .user.login: zoeMap the response to typed models in one line:
ts
import { defineModel, type Infer } from "@flattype/models";
const User = defineModel({ id: "number", fullName: "string", createdAt: "date" });
type User = Infer<typeof User>; // { id: number; fullName: string; createdAt: Date }
const users = User.parseList(await res.json()); // full_name → fullName, ISO string → DateOr let both ends speak Flat Notation, with JSON for every other client:
ts
import { flatExpress, flatFetch } from "@flattype/http";
app.use(flatExpress()); // server: res.json() answers in Flat Notation when the client asks
const users = await flatFetch("/api/users", { model: User, list: true }); // client: User[]And in React, Vue, Angular, Svelte or Solid:
tsx
import { useFlat } from "@flattype/react"; // or "@flattype/vue": the same API, with refs
const { data: users, loading, error } = useFlat("/api/users", { model: User, list: true }); // User[] | undefined
// Angular: signals on top of HttpClient
users = flatResource(() => "/api/users", { model: User, list: true }); // users.value(): User[] | undefined
// Svelte: a store, $users.data; Solid: a resource
const users = flatStore("/api/users", { model: User, list: true });
const [users] = createFlatResource(() => "/api/users", { model: User, list: true });Why FlatType
- About half the size on list payloads. A list of 1,000 users is 45% smaller than minified JSON and 50% smaller than TOON, because keys are written once and nested fields are flattened into the table header.
- Made for LLM context. You pay for every character you send to Claude or another model. Flat Notation is plain
key: valuetext that both models and people read easily, anddecode(text, { strict: false })recovers data from model replies, with a warning for every line it had to repair. - Typed models without boilerplate. Declare the fields you want and get full TypeScript types. snake_case keys, numeric strings, ISO dates, defaults and nullable fields are handled for you, and every problem is reported with its path.
- Drop-in for your API. One middleware for Express, Fastify, Hono or any fetch-API server. Clients that ask get Flat Notation, everyone else still gets JSON, and big lists can be streamed row by row.
- Lossless.
decode(encode(x))always equalsJSON.parse(JSON.stringify(x)). Thousands of random values check this on every test run. - Small and safe. The core is 6.2 KB min+gz, with no third-party dependencies. It's ESM + CJS with full types, works in the browser and Node 18+, and guards against
__proto__pollution.
When JSON is the better choice
- One small object per request. Gzip already removes most of JSON's overhead on the wire, so a three-field object saves only a few bytes. The savings come from lists.
- Data that only machines read and you don't control both ends. Every JSON tool understands JSON. Flat Notation pays off where you own the client and server, or where the text goes to an LLM.
- Mixing the two formats in one value. Decode Flat Notation with
decodeand JSON withJSON.parse; a JSON string inside a Flat Notation value is just a string.
Packages
| Package | What it does | Size (min+gz) |
|---|---|---|
@flattype/core | encode, decode, createDecoder, flatten, unflatten, stats, and a <script> build for CDNs | 6.2 KB |
@flattype/models | defineModel, infer, toSource: typed models from JSON or Flat Notation | +3.2 KB |
@flattype/http | flatFetch, createClient, flatAxios, streamRows, and flatExpress / flatFastify / flatHono | +2.3 KB |
@flattype/react | useFlat, useFlatSuspense, useFlatStream, useModel, FlatProvider | +1.4 KB |
@flattype/vue | The same composables for Vue 3, with reactive URLs, plus the createFlat() plugin | +1.2 KB |
@flattype/angular | flatInterceptor for HttpClient, the signal-based flatResource / flatStream, the flatModel pipe | +1.8 KB |
@flattype/svelte | Svelte stores (flatStore, flatStreamStore, modelStore), and /solid with createFlatResource / createFlatStream | +1.1 KB / +0.9 KB |
@flattype/ai | toPrompt, fromModelOutput, countTokens, and MCP / Claude tool results | +1.3 KB |
@flattype/cli | The flattype command: encode, decode, stats, and gen (models from samples or OpenAPI) | Node tool |
@flattype/shared | Shared TypeScript types | types only |
flattype on PyPI | The same encode / decode for Python, FlatMiddleware for FastAPI and any ASGI app, and the LLM helpers | Python 3.9+ |
Everything is documented at karammm.github.io/flattype/docs, and you can try the format on your own data in the playground. See the roadmap.