Typed Data Format

PAKT — data that knows its own type

Human-authorable. Streaming. Self-describing. Every value carries its type — no inference, no ambiguity, no surprises.

app-name:str     = 'midwatch'
version:(int, int, int) = (2, 14, 0)
active:bool      = true
server:{host:str, port:int} = { 'localhost', 8080 }

# Stream events with << — one per line
events:[{ts:ts, level:|info, warn, error|}] <<
    { 2026-06-01T14:30:00Z, |info }
    { 2026-06-01T14:31:00Z, |warn }

You already know most of this

If you've written JSON, YAML, or TOML, PAKT will feel right at home. Same concepts — keys, values, nesting — just with types baked in.

{ }

Keys and values

Named fields with values, just like JSON objects or YAML mappings. Write name:str = 'hello' and you're done.

⟨⟩

Nesting and structure

Maps inside maps, tuples, lists — compose types naturally. server:{host:str, port:int} reads like you'd expect.

#

Comments

Line comments with #, just like YAML and TOML. Document your data inline — it's meant to be read by humans.

Here's where it gets fun

PAKT isn't just another config format. Types and streaming make it something genuinely different.

:

Every value has a type

Type annotations are mandatory — count:int = 42, not just count = 42. The producer declares intent, the parser enforces it. No guessing.

Self-describing data

Type annotations are validated at parse time — producers assert types, parsers enforce them. No schema negotiation, no external files needed.

<<

Packs with <<

Open a pack with << and emit records one by one. No enclosing array, no delimiters between items. Data flows as it's produced.

PAKT vs JSON

Same data, different philosophy. PAKT makes types explicit and structure visible at a glance.

PAKT

server:{host:str, port:int} = {
    'localhost'
    8080
}
debug:bool = false

JSON

{
  "server": {
    "host": "localhost",
    "port": 8080
  },
  "debug": false
}

Types are built in. Structure is explicit. Every value is self-describing.

How it works

Three steps, one pipeline.

1

Write

Author .pakt files with type-annotated fields. Human-readable, self-documenting, impossible to get wrong.

2

Parse

The streaming parser emits events as it reads — no full-unit buffering. One event per call, constant memory per nesting level.

3

Consume

Iterate properties with UnitReader, stream pack elements with PackItems[T], or unmarshal an entire unit with UnmarshalNew[T].

Pick your ecosystem

First-class implementations for Go and .NET. Same spec, idiomatic for each platform.

Go

Streaming UnitReader with iter.Seq iterators, generic ReadValue[T], pack streaming, custom converters.

go install github.com/trippwill/pakt@latest
UnitReader + PackItems UnmarshalNew[T] CLI tool
Get started →

.NET

New

Source-generated serialization modeled after System.Text.Json. Ref-struct tokenizer, zero-copy, forward-only.

dotnet add package Pakt
Source generator PaktReader / PaktWriter Async packs
Get started →