LNP is a compact, deterministic, length-prefixed data serialization format. It encodes structured data (objects, arrays, primitives, and binary) using a minimal grammar and unambiguous parsing rules. The design goal is to provide a simple, stream-friendly, and implementation-agnostic protocol suitable for storage, messaging, and structured binary or textual data exchange.
Every value is encoded as:
<type-char><byte-length>:<payload-bytes>
Where:
<type-char>— a single ASCII character indicating the value type<byte-length>— decimal ASCII length of the payload in bytes:— required separator<payload-bytes>— exactly<byte-length>bytes of content
This eliminates the need for delimiters, escaping, or structural markers. All nested values remain valid LNP values.
| Type | Char | Payload Description |
|---|---|---|
| Object | o |
Sequence of entries <klen>:<kbytes><value> |
| Array | a |
Sequence of complete LNP values |
| String | s |
Raw UTF-8 string bytes |
| Number | n |
ASCII numeric representation (-12, 3.14, etc.) |
| Boolean | b |
Single byte: t or f |
| Null | N |
Must have zero-length payload |
| Bytes | B |
Base64-encoded bytes (or raw bytes, implementation defined) |
Object payloads consist of repeated entries:
<key-length>:<key-bytes><value>
Where <value> is a complete LNP value including its type and length prefix.
Example:
o27:name4:Johnage2:n24
Represents:
{
"name": "John",
"age": 24
}Arrays concatenate multiple full LNP values inside the payload:
a14s3:foos3:bar
Equivalent to:
["foo", "bar"]s5:hello
n4:-2.5
b1:t // true
b1:f // false
N0:
B12:SGVsbG8gV29ybGQ=
value = typeChar length ":" payload ;
typeChar = "o" | "a" | "s" | "n" | "b" | "N" | "B" ;
length = digits ;
digits = digit { digit } ;
payload = bytes(length) ;
object = "o" length ":" { entry } ;
entry = digits ":" bytes(keylen) value ;
array = "a" length ":" { value } ;
bytes(n) means exactly n raw bytes, without interpretation.
- Deterministic: identical input always produces identical output.
- Non-ambiguous: no quoting, escaping, or delimiter rules.
- Streaming-friendly: parser can process values incrementally.
- Compact: minimal overhead, fixed structural cost.
- Easy to implement: simple state machine and length-bounded reads.
- Suitable for hashing: stable representation ideal for merkle-tree or content-addressed storage.
- Official Node.js encoder/decoder
- Reference test suite
- Implementations in Rust, Go, Python
- Optional binary variant (BLNP)