Skip to content

Getting started ​

bash
npm install @flattype/core @flattype/models @flattype/http
ts
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, Delhi

Objects 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: zoe

Map 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 → Date

Or 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: value text that both models and people read easily, and decode(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 equals JSON.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 decode and JSON with JSON.parse; a JSON string inside a Flat Notation value is just a string.

Packages ​

PackageWhat it doesSize (min+gz)
@flattype/coreencode, decode, createDecoder, flatten, unflatten, stats, and a <script> build for CDNs6.2 KB
@flattype/modelsdefineModel, infer, toSource: typed models from JSON or Flat Notation+3.2 KB
@flattype/httpflatFetch, createClient, flatAxios, streamRows, and flatExpress / flatFastify / flatHono+2.3 KB
@flattype/reactuseFlat, useFlatSuspense, useFlatStream, useModel, FlatProvider+1.4 KB
@flattype/vueThe same composables for Vue 3, with reactive URLs, plus the createFlat() plugin+1.2 KB
@flattype/angularflatInterceptor for HttpClient, the signal-based flatResource / flatStream, the flatModel pipe+1.8 KB
@flattype/svelteSvelte stores (flatStore, flatStreamStore, modelStore), and /solid with createFlatResource / createFlatStream+1.1 KB / +0.9 KB
@flattype/aitoPrompt, fromModelOutput, countTokens, and MCP / Claude tool results+1.3 KB
@flattype/cliThe flattype command: encode, decode, stats, and gen (models from samples or OpenAPI)Node tool
@flattype/sharedShared TypeScript typestypes only
flattype on PyPIThe same encode / decode for Python, FlatMiddleware for FastAPI and any ASGI app, and the LLM helpersPython 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.

Released under the MIT License.