oramasearch/seqproto

Extremely fast, compact, binary serialization/deserialization for your structured data

JavaScript

125

41 commits

updated Mar 27, 2024

See the code

README

SeqProto

Nodejs npm package minimized gzipped size (select exports)

This library provides a simple way to serialize and deserialize objects in binary format.

Why another serialization library?

While I have been writing this library, I have in mind the following main goals:

  • Runtime independent - I want to have a library that runs in every javascript runtime, from Node.JS, through browsers, to CloudFlare.
  • Performance - I want to have a library that is fast and easy to use.
  • Small size - I want to have a library that is small and easy to use.
  • Customizable - Due to the JavaScript nature, the data structures are limited.
  • TypeScript support - I want to have a library that is easy to use in TypeScript.

Installation

Seqproto works in any JavaScript environment. You can install it via npm:

npm install seqproto

Or via CDN:

import { createSer, createDes } from 'https://unpkg.com/seqproto@latest/dist/esm/index.js'

Usage

Examples

For more examples, see the examples directory.

import { createSer, createDes } from 'seqproto'

// Create a serializer
const ser = createSer()

// Serialize some data
ser.serializeBoolean(true)
ser.serializeUInt32(42)
ser.serializeFloat32(-0.5)
ser.serializeString('hello world')
ser.serializeArray([1, 2, 3], (ser, n) => ser.serializeUInt32(n))

// Get ArrayBuffer with serialized data
const buffer = ser.getBuffer()

// Create a deserializer
const des = createDes(buffer)

// Deserialize data
const b = des.deserializeBoolean()
const i = des.deserializeUInt32()
const f = des.deserializeFloat32()
const s = des.deserializeString()
const a = des.deserializeArray((des) => des.deserializeUInt32())

console.log({ b, i, f, s, a })

Object

import type { Ser, Des } from 'seqproto'
import { createSer, createDes } from 'seqproto'

interface Todo {
  id: number
  userId: number
  title: string
  completed: boolean
}

function serializeTodo (ser: Ser, todo: Todo) {
  ser.serializeUInt32(todo.id)
  ser.serializeUInt32(todo.userId)
  ser.serializeString(todo.title)
  ser.serializeBoolean(todo.completed)
}

function deserializeTodo (des: Des): Todo {
  const id = des.deserializeUInt32()
  const userId = des.deserializeUInt32()
  const title = des.deserializeString()
  const completed = des.deserializeBoolean()
  return { id, userId, title, completed }
}

const ser: Ser = createSer()

serializeTodo(ser, {
  id: 1,
  userId: 1,
  title: 'hello',
  completed: false,
})

const buffer = ser.getBuffer()

const des: Des = createDes(buffer)
const todo = deserializeTodo(des)

console.log(JSON.stringify(todo, null, 2))

Array of object

import type { Ser, Des } from 'seqproto'
import { createSer, createDes } from 'seqproto'

let buffer

// Serialize
const todos = [
  { userId: 1, id: 1, completed: false, title: "delectus aut autem" },
  { userId: 1, id: 2, completed: true, title: "quis ut nam facilis et officia qui" }
]

const ser: Ser = createSer()

ser.serializeArray(todos, (ser, todo) => {
  ser.serializeUInt32(todo.id)
  ser.serializeUInt32(todo.userId)
  ser.serializeString(todo.title)
  ser.serializeBoolean(todo.completed)
})

buffer = ser.getBuffer()

// Deserialize
const des: Des = createDes(buffer)

const deserializedTodos = des.deserializeArray((des) => {
  const id = des.deserializeUInt32()
  const userId = des.deserializeUInt32()
  const title = des.deserializeString()
  const completed = des.deserializeBoolean()
  return { id, userId, title, completed }
})

console.log(deserializedTodos)

API

This library exports the following functions:

  • createSer()/createSer({ bufferSize: number }): creates a new serializer.
  • createDes(buffer): creates a new deserializer.
  • ser.reset(): reset the serializer.
  • ser.serializeBoolean(b): serializes a boolean value.
  • des.deserializeBoolean(): deserializes a boolean value.
  • ser.serializeUInt32(uint32): serializes a 32-bit unsigned integer.
  • des.deserializeUInt32(uint32): deserializes a 32-bit unsigned integer.
  • ser.serializeNumber(n): serializes a 32-bit unsigned integer or signed integer or float.
  • des.deserializeNumber(n): deserializes a 32-bit unsigned integer or signed integer or float.
  • ser.serializeString(string): serializes a string.
  • des.deserializeString(): deserializes a string.
  • ser.serializeArray(array, (ser, item) => { ... }): serializes an array.
  • des.deserializeArray((des) => { ... }): deserializes an array.
  • ser.serializeIterable(iterable, (ser, item) => { ... }): serializes an iterable.
  • des.deserializeIterable((des) => { ... }): deserializes an iterable.
  • ser.serializeFloat32(float32): serializes float 32bit.
  • des.deserializeFloat32(): deserializes float 32bit.
  • ser.getBuffer(): returns the serialized buffer.

Benchmarks

We created 3 different benchmarks to compare the performance of SeqProto with JSON and Avro.

Isolated benchmark

You can run the benchmarks with the following command:

npm run benchmark:serdes

Serialization / Deserialization:

nameopsmarginpercentSlower
seqproto297640.720
protobuf136980.1953.98
avro242040.1418.68
cbor8030.2297.3
cborx97070.3267.39
msgpack68570.0676.96
msgpackr104490.2764.89
JSON144340.0751.51

Http benchmark

You can run the benchmarks using 2 shells.

cd bechmarks/e2e
pnpm install
pnpm start
cd bechmarks/e2e
pnpm run autocannon:json
pnpm run autocannon:seqproto
pnpm run autocannon:avro
typereq (in 10s)Avg req/secAvg Bytes/SecAvg Latency (ms)
JSON164k148922750.11
SeqProto269k26865.63210.01
Avro197k17926.551690.04

e2e benchmark

You can run the benchmarks with the following command:

cd bechmarks/e2e
pnpm install
pnpm start

And go to http://localhost:3000/public/index.html.

iterationparallelismtypems
101JSON30.69999998807907
101SeqProto25.600000023841858
101Avro30.399999976158142
1001JSON108.80000001192093
1001SeqProto96.80000001192093
1001Avro96
1003JSON162.10000002384186
1003SeqProto152.4000000357628
1003Avro167.5
1006JSON277.19999998807907
1006SeqProto263.30000001192093
1006Avro308.19999998807907

Contributing

Contributions are welcome! Please open an issue if you have any ideas for improvement or found a bug.

License

Apache-2.0

Contributors

allevo

32 commits

micheleriva

5 commits

teobmg

4 commits

oramasearch/seqproto

Extremely fast, compact, binary serialization/deserialization for your structured data

JavaScript

125

41 commits

updated Mar 27, 2024

See the code

README

SeqProto

Nodejs npm package minimized gzipped size (select exports)

This library provides a simple way to serialize and deserialize objects in binary format.

Why another serialization library?

While I have been writing this library, I have in mind the following main goals:

  • Runtime independent - I want to have a library that runs in every javascript runtime, from Node.JS, through browsers, to CloudFlare.
  • Performance - I want to have a library that is fast and easy to use.
  • Small size - I want to have a library that is small and easy to use.
  • Customizable - Due to the JavaScript nature, the data structures are limited.
  • TypeScript support - I want to have a library that is easy to use in TypeScript.

Installation

Seqproto works in any JavaScript environment. You can install it via npm:

npm install seqproto

Or via CDN:

import { createSer, createDes } from 'https://unpkg.com/seqproto@latest/dist/esm/index.js'

Usage

Examples

For more examples, see the examples directory.

import { createSer, createDes } from 'seqproto'

// Create a serializer
const ser = createSer()

// Serialize some data
ser.serializeBoolean(true)
ser.serializeUInt32(42)
ser.serializeFloat32(-0.5)
ser.serializeString('hello world')
ser.serializeArray([1, 2, 3], (ser, n) => ser.serializeUInt32(n))

// Get ArrayBuffer with serialized data
const buffer = ser.getBuffer()

// Create a deserializer
const des = createDes(buffer)

// Deserialize data
const b = des.deserializeBoolean()
const i = des.deserializeUInt32()
const f = des.deserializeFloat32()
const s = des.deserializeString()
const a = des.deserializeArray((des) => des.deserializeUInt32())

console.log({ b, i, f, s, a })

Object

import type { Ser, Des } from 'seqproto'
import { createSer, createDes } from 'seqproto'

interface Todo {
  id: number
  userId: number
  title: string
  completed: boolean
}

function serializeTodo (ser: Ser, todo: Todo) {
  ser.serializeUInt32(todo.id)
  ser.serializeUInt32(todo.userId)
  ser.serializeString(todo.title)
  ser.serializeBoolean(todo.completed)
}

function deserializeTodo (des: Des): Todo {
  const id = des.deserializeUInt32()
  const userId = des.deserializeUInt32()
  const title = des.deserializeString()
  const completed = des.deserializeBoolean()
  return { id, userId, title, completed }
}

const ser: Ser = createSer()

serializeTodo(ser, {
  id: 1,
  userId: 1,
  title: 'hello',
  completed: false,
})

const buffer = ser.getBuffer()

const des: Des = createDes(buffer)
const todo = deserializeTodo(des)

console.log(JSON.stringify(todo, null, 2))

Array of object

import type { Ser, Des } from 'seqproto'
import { createSer, createDes } from 'seqproto'

let buffer

// Serialize
const todos = [
  { userId: 1, id: 1, completed: false, title: "delectus aut autem" },
  { userId: 1, id: 2, completed: true, title: "quis ut nam facilis et officia qui" }
]

const ser: Ser = createSer()

ser.serializeArray(todos, (ser, todo) => {
  ser.serializeUInt32(todo.id)
  ser.serializeUInt32(todo.userId)
  ser.serializeString(todo.title)
  ser.serializeBoolean(todo.completed)
})

buffer = ser.getBuffer()

// Deserialize
const des: Des = createDes(buffer)

const deserializedTodos = des.deserializeArray((des) => {
  const id = des.deserializeUInt32()
  const userId = des.deserializeUInt32()
  const title = des.deserializeString()
  const completed = des.deserializeBoolean()
  return { id, userId, title, completed }
})

console.log(deserializedTodos)

API

This library exports the following functions:

  • createSer()/createSer({ bufferSize: number }): creates a new serializer.
  • createDes(buffer): creates a new deserializer.
  • ser.reset(): reset the serializer.
  • ser.serializeBoolean(b): serializes a boolean value.
  • des.deserializeBoolean(): deserializes a boolean value.
  • ser.serializeUInt32(uint32): serializes a 32-bit unsigned integer.
  • des.deserializeUInt32(uint32): deserializes a 32-bit unsigned integer.
  • ser.serializeNumber(n): serializes a 32-bit unsigned integer or signed integer or float.
  • des.deserializeNumber(n): deserializes a 32-bit unsigned integer or signed integer or float.
  • ser.serializeString(string): serializes a string.
  • des.deserializeString(): deserializes a string.
  • ser.serializeArray(array, (ser, item) => { ... }): serializes an array.
  • des.deserializeArray((des) => { ... }): deserializes an array.
  • ser.serializeIterable(iterable, (ser, item) => { ... }): serializes an iterable.
  • des.deserializeIterable((des) => { ... }): deserializes an iterable.
  • ser.serializeFloat32(float32): serializes float 32bit.
  • des.deserializeFloat32(): deserializes float 32bit.
  • ser.getBuffer(): returns the serialized buffer.

Benchmarks

We created 3 different benchmarks to compare the performance of SeqProto with JSON and Avro.

Isolated benchmark

You can run the benchmarks with the following command:

npm run benchmark:serdes

Serialization / Deserialization:

nameopsmarginpercentSlower
seqproto297640.720
protobuf136980.1953.98
avro242040.1418.68
cbor8030.2297.3
cborx97070.3267.39
msgpack68570.0676.96
msgpackr104490.2764.89
JSON144340.0751.51

Http benchmark

You can run the benchmarks using 2 shells.

cd bechmarks/e2e
pnpm install
pnpm start
cd bechmarks/e2e
pnpm run autocannon:json
pnpm run autocannon:seqproto
pnpm run autocannon:avro
typereq (in 10s)Avg req/secAvg Bytes/SecAvg Latency (ms)
JSON164k148922750.11
SeqProto269k26865.63210.01
Avro197k17926.551690.04

e2e benchmark

You can run the benchmarks with the following command:

cd bechmarks/e2e
pnpm install
pnpm start

And go to http://localhost:3000/public/index.html.

iterationparallelismtypems
101JSON30.69999998807907
101SeqProto25.600000023841858
101Avro30.399999976158142
1001JSON108.80000001192093
1001SeqProto96.80000001192093
1001Avro96
1003JSON162.10000002384186
1003SeqProto152.4000000357628
1003Avro167.5
1006JSON277.19999998807907
1006SeqProto263.30000001192093
1006Avro308.19999998807907

Contributing

Contributions are welcome! Please open an issue if you have any ideas for improvement or found a bug.

License

Apache-2.0

Contributors

allevo

32 commits

micheleriva

5 commits

teobmg

4 commits

Languages

JavaScript

96.2%

TypeScript

3.2%