Binpat

Binpat simplifies parsing binary data in JavaScript by allowing you to define the data structure using declarative patterns.
- Declarative: Define what your data looks like, no more manual
DataView operations and offsets.
- Readable: Patterns often closely resemble the desired output object structure.
- Type Safe: Built with TypeScript, providing inferred return types based on your patterns.
Features
- Common types:
u8...u64, i8...i64, f16...f64, bool, string, bitfield and array(pattern, size).
- Conditional parsing with
ternary(condition, truthy, falsy).
- Transform parsed values using
convert(pattern, fn).
- Control parsing offset with
skip(offset), seek(offset) and peek(offset, pattern).
- Modify output structure with
omit() (exclude fields) and spread() (flatten fields).
Installation
Install from 
npm i binpat
Install from 
deno add jsr:@aho/binpat
Import from CDN
or

import Binpat from 'https://cdn.jsdelivr.net/npm/binpat/dist/binpat.js';
import Binpat from 'https://unpkg.com/binpat/dist/binpat.js';
Usage
import Binpat, { u8, u16, string, array } from 'binpat';
const filePattern = {
fileType: string(4),
version: u8(),
numRecords: u16(),
records: array({ id: u8(), value: u8() }, (ctx) => ctx.data.numRecords)
};
const binpat = new Binpat(filePattern);
const sampleData = new Uint8Array([
0x44, 0x41, 0x54, 0x41,
0x01,
0x00, 0x02,
0x01, 0x64,
0x02, 0xC8
]);
const result = binpat.exec(sampleData.buffer);
console.log(result);
Find more complex examples in the examples directory.
API
Find the full API details in the docs.
new Binpat(pattern[, option])
pattern can be native object, native array or binpat functions.
new Binpat({ foo: u8() });
new Binpat([u8(), u8()]);
new Binpat(array(u8(), 10));
option can set the global endianness:
{
endian: 'big',
}
u8, u16, u32, u64, i8, i16, i32, i64, f16, f32, f64
All these functions except u8 and i8 accept a boolean param to set endian. They will use the global endianness by default.
import Binpat, { u8, u16 } from 'binpat';
const binpat = new Binpat({
a: u8(),
b: u16(),
c: u16(true),
});
bool()
import Binpat, { bool } from 'binpat';
const binpat = new Binpat({
flag: bool(),
});
string(size[, encoding])
You can parse binary to string with a specific text encoding:
const utf8 = Uint8Array.from(new TextEncoder().encode('binpat'));
console.log(new Binpat(string(6)).exec(utf8.buffer));
const gbk = new Uint8Array([214, 208, 206, 196]);
console.log(new Binpat(string(4, 'gbk')).exec(gbk.buffer));
And the string size can be read from context dynamicly:
new Binpat({
size: u8(),
text: string((ctx) => ctx.data.size),
});
bitfield(layout[, option])
Define layout with native object:
import Binpat, { bitfield, omit } from 'binpat';
const binpat = new Binpat(bitfield({
a: 3,
b: bitfield.u(3),
[omit('padding')]: 4,
c: bitfield.i(5),
d: bitfield.bool(),
}));
const { buffer } = new Uint8Array([0b01010101, 0b11001100]);
console.log(binpat.exec(buffer));
option can set endian and Bit numbering:
{
endian: 'big',
first: 'MSb',
}
array(pattern, size)
pattern can be native object or binpat functions.
If the pattern is a primitive type, it will return a TypedArray instance.
import Binpat, { array, u16, u8 } from 'binpat';
const binpat = new Binpat({
bar: array({ x: u8(), y: u8() }, 4),
foo: array(u16(), 4),
});
And the array size can be read from context dynamicly:
new Binpat({
count: u32(),
items: array(u8(), (ctx) => ctx.data.count),
});
ternary(condition, truthy[, falsy])
It works like condition ? truthy : falsy
import Binpat, { ternary, bool, u16, u8 } from 'binpat';
const binpat = new Binpat({
flag: bool(),
value: ternary(
(ctx) => ctx.data.flag,
[u8(), u8()],
[u16()],
),
});
console.log(binpat.exec(new Uint8Array([1, 0, 0]).buffer));
console.log(binpat.exec(new Uint8Array([0, 0, 0]).buffer));
convert(pattern, fn)
Convert the result value with custom function:
import Binpat, { convert, u16 } from 'binpat';
const binpat = new Binpat({
type: convert(u16(), (value) => ['', 'ico', 'cur'][value] || 'unknown'),
});
console.log(binpat.exec(new Uint8Array([0, 1]).buffer));
console.log(binpat.exec(new Uint8Array([0, 2]).buffer));
console.log(binpat.exec(new Uint8Array([0, 0]).buffer));
seek(offset)
Move current offset to the given offset.
offset can be number or a function returns a number.
import Binpat, { seek, omit, u8 } from 'binpat';
const binpat = new Binpat({
foo: u8(),
[omit('padding')]: seek((ctx) => ctx.offset + 4),
bar: u8(),
});
const { buffer } = new Uint8Array([1, 0, 0, 0, 0, 2]);
console.log(binpat.exec(buffer));
peek(offset, pattern)
Reads pattern from the given offset, and doesn't move current offset.
offset can be number or a function returns a number.
import Binpat, { array, peek, u8 } from 'binpat';
const binpat = new Binpat(array({
size: u32(),
address: u32(),
data: peek(
(ctx) => ctx.data.address,
array(u8(), (ctx) => ctx.data.size),
),
}, 4));
skip(offset)
Move forward with the given offset.
skip(x) is same as seek((ctx) => ctx.offset + x)
import Binpat, { skip, omit, u8 } from 'binpat';
const binpat = new Binpat({
foo: u8(),
[omit('padding')]: skip(4),
bar: u8(),
});
const { buffer } = new Uint8Array([1, 0, 0, 0, 0, 2]);
console.log(binpat.exec(buffer));
Omit the key-value in result.
comment can be any value.
import Binpat, { omit, u16 } from 'binpat';
const binpat = new Binpat({
[omit('reserved')]: u16(),
type: u16(),
count: u16(),
});
const { buffer } = new Uint8Array([0, 0, 0, 1, 0, 1]);
console.log(binpat.exec(buffer));
Due to a TypeScript bug, you'll get wrong type with omit(). To solve it, you can use string literal starts with // as object key:
const binpat = new Binpat({
'// reserved': u16(),
type: u16(),
count: u16(),
'// another reserved': u16(),
});
It works like spread syntax ..., and usually be used with ternary().
import Binpat, { spread, ternary, bool, u8 } from 'binpat';
const binpat = new Binpat({
flag: bool(),
[spread()]: ternary(
(ctx) => ctx.data.flag,
{ truthy: u8() },
{ falsy: u8() },
),
});
console.log(binpat.exec(new Uint8Array([1, 0]).buffer));
console.log(binpat.exec(new Uint8Array([0, 0]).buffer));
And like omit(), you can use string literal starts with ... as object key to get correct type inference:
const binpat = new Binpat({
flag: bool(),
'...foo': ternary(
(ctx) => ctx.data.flag,
{ truthy: u8() },
{ falsy: u8() },
),
});