binpat
v0.1.3
Published
Parse binary data using declarative patterns.
Downloads
151
Maintainers
Readme
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
DataViewoperations 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,bitfieldandarray(pattern, size). - Conditional parsing with
ternary(condition, truthy, falsy). - Transform parsed values using
convert(pattern, fn). - Control parsing offset with
skip(offset),seek(offset)andpeek(offset, pattern). - Modify output structure with
omit()(exclude fields) andspread()(flatten fields).
Installation
npm i binpatdeno add jsr:@aho/binpatimport 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), // e.g., 'DATA'
version: u8(), // e.g., 1
numRecords: u16(), // e.g., 2 records
// Read 'numRecords' count of { id: u8, value: u8 }
records: array({ id: u8(), value: u8() }, (ctx) => ctx.data.numRecords)
};
const binpat = new Binpat(filePattern);
const sampleData = new Uint8Array([
0x44, 0x41, 0x54, 0x41, // 'DATA'
0x01, // version 1
0x00, 0x02, // numRecords 2 (Big Endian)
0x01, 0x64, // Record 1: id=1, value=100
0x02, 0xC8 // Record 2: id=2, value=200
]);
const result = binpat.exec(sampleData.buffer);
console.log(result);
/*
{
fileType: 'DATA',
version: 1,
numRecords: 2,
records: [
{ id: 1, value: 100 },
{ id: 2, value: 200 },
],
}
*/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:
{
// The global endianness.
// 'big' | 'little'
endian: 'big', // default
}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(),
// use little endian
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));
// 'binpat'
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, // unsigned, 3 bits
b: bitfield.u(3), // unsigned, 3 bits
[omit('padding')]: 4, // padding, 4 bits
c: bitfield.i(5), // signed, 5 bits
d: bitfield.bool(), // boolean, 1 bit
}));
// aaabbbpp ppcccccd
const { buffer } = new Uint8Array([0b01010101, 0b11001100]);
console.log(binpat.exec(buffer));
// {
// a: 2, // 0b010
// b: 5, // 0b101
// // 0b0111 (padding)
// c: 6, // 0b00110
// d: false, // 0b0
// }option can set endian and Bit numbering:
{
// The endianness of the bitfield.
// Allow values: 'big' | 'little'
// If not specified, it will use the global endianness.
endian: 'big',
// Which bit comes first, allow values:
// + 'MSb': Most Significant Bit (left-to-right).
// + 'LSb': Least Significant Bit (right-to-left).
// If not specified:
// + When endian is 'big', the first bit is 'MSb'.
// + When endian is 'little', the first bit is 'LSb'.
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({
// pattern can be object
bar: array({ x: u8(), y: u8() }, 4),
// It will return Uint16Array
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));
// { flag: true, value: [0, 0] }
console.log(binpat.exec(new Uint8Array([0, 0, 0]).buffer));
// { flag: false, value: [0] }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));
// { type: 'ico' }
console.log(binpat.exec(new Uint8Array([0, 2]).buffer));
// { type: 'cur' }
console.log(binpat.exec(new Uint8Array([0, 0]).buffer));
// { type: 'unknown' }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));
// { foo: 1, bar: 2 }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));
// { foo: 1, bar: 2 }omit(comment?)
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));
// { type: 1, count: 1 }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(),
// and you should avoid duplicate key
'// another reserved': u16(),
});spread(comment?)
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));
// { flag: true, truthy: 0 }
console.log(binpat.exec(new Uint8Array([0, 0]).buffer));
// { flag: false, falsy: 0 }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() },
),
});