fatline

Wire Format

How fatline encodes types on the wire

Understanding the wire format helps you debug serialization issues and make informed decisions about when to use fatline.

The Problem

JSON supports 6 types: string, number, boolean, null, array, object. Everything else—Date, Set, Map, BigInt—must be encoded somehow.

SuperJSON stores type information in a separate metadata object with paths:

{
  "json": { "createdAt": "2024-01-01T00:00:00.000Z" },
  "meta": { "values": { "createdAt": ["Date"] } }
}

fatline uses inline markers—type information lives with the data:

{ "createdAt": ["~", 1, 1704067200000] }

Tuple Format

fatline encodes extended types as 3-element arrays:

["~", typeId, encodedValue]
  • "~" — Marker prefix (distinguishes from regular arrays)
  • typeId — Number identifying the type
  • encodedValue — The encoded value

Examples:

// Date → milliseconds timestamp
new Date('2024-01-01')
["~", 1, 1704067200000]

// Set → array of values
new Set(['a', 'b'])
["~", 4, ["a", "b"]]

// Map → array of [key, value] entries
new Map([['x', 1]])
["~", 3, [["x", 1]]]

// BigInt → string
123n
["~", 2, "123"]

// undefined (preserved, unlike JSON)
undefined
["~", 0, null]

Type IDs

IDTypeEncoded As
0undefinednull
1Datemilliseconds timestamp
2BigIntstring
3Map[[key, value], ...]
4Set[...values]
5NaNnull
6Infinitynull
7-Infinitynull
8RegExp[source, flags]
9URLstring
10-18TypedArraysbase64 string
19Circular refreference index
20Reserved
21+Custom typesvaries

Passthrough Mode

When data contains no extended types, fatline produces standard JSON:

serialize({ name: 'Alice', count: 42 })
// → '{"name":"Alice","count":42}'

No markers, no overhead. The deserialize function handles both formats.

Nested Extended Types

Extended types can contain other extended types:

const data = {
  cache: new Map([
    ['users', new Set([1, 2, 3])],
    ['updated', new Date()]
  ])
}

// Wire format:
{
  "cache": ["~", 3, [
    ["users", ["~", 4, [1, 2, 3]]],
    ["updated", ["~", 1, 1704067200000]]
  ]]
}

Circular References

When circular: 'ref' is set, circular references encode as pointers:

const obj = { name: 'root' }
obj.self = obj

serialize(obj, { circular: 'ref' })
// → { "name": "root", "self": ["~", 19, 0] }
//                                      ^ reference to object #0

Debug Mode

For human-readable output during development:

serialize(data, { debug: true })
{
  "createdAt": "⟨Date: 2024-01-01T00:00:00.000Z⟩",
  "roles": "⟨Set: admin,user⟩"
}

Next Steps

On this page