qtdatastream-web
A TypeScript (de)serializer for Qt's QDataStream
format that runs natively in the browser — built only on Uint8Array,
DataView, BigInt, and TextEncoder/TextDecoder. No Buffer, no Node
streams, no polyfills.
Supported types: QBool, QShort, QInt, QInt64, QUInt, QUInt64,
QDouble, QMap, QList, QString, QVariant, QStringList, QByteArray,
QUserType, QDateTime, QTime, QChar, QInvalid.
Install
npm install qtdatastream-web
The package ships ES modules and TypeScript declarations. There is no default runtime dependency.
Quick start
import { read, write } from 'qtdatastream-web';
// Serialize a value to a Uint8Array...
const bytes = write({ AString: 'BString', CString: 42 });
// ...and read it back.
const value = read(bytes); // { AString: 'BString', CString: 42 }
write/read operate on a single QVariant with no framing — convenient for
reading from and writing to files. Input to read may be a Uint8Array,
ArrayBuffer, any TypedArray, or a number[]; output is always a
Uint8Array.
Reading a file in the browser
import { read } from 'qtdatastream-web';
const file = input.files[0];
const value = read(await file.arrayBuffer());
Writing a file in the browser
import { write } from 'qtdatastream-web';
const blob = new Blob([write(value)], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
API
import {
read, write, // single QVariant, no length prefix
readAll, // read every QVariant until the buffer is exhausted
readPacket, writePacket, // QVariant prefixed with a 4-byte big-endian length
ReadBuffer, // sequential big-endian reader over a byte buffer
types, // all Q* type classes + Types enum
util, // str(), date <-> Julian day, deep mapping
bytes, // low-level byte/encoding helpers
serialization, // Serializable / serialize decorators
} from 'qtdatastream-web';
Submodules are also importable directly, e.g. qtdatastream-web/types.
Type inference
JavaScript values are coerced to Qt types automatically.
| JavaScript | QClass |
|---|---|
| string | QString |
| number | QUInt (configurable) |
| bigint | QInt64 |
| boolean | QBool |
| Array | QList<QVariant<?>> |
| Date | QDateTime |
| Map | QMap |
| Object | QMap |
Force any value into a specific Qt type with <QClass>.from:
import { types } from 'qtdatastream-web';
const qbytearray = types.QByteArray.from('hello'); // string written as a QByteArray
Change the default class plain numbers coerce to:
import { types } from 'qtdatastream-web';
types.QVariant.coerceNumbersTo(types.Types.DOUBLE); // numbers now serialize as QDouble
Reading back to JavaScript
| QClass | JavaScript |
|---|---|
| QString | string |
| QUInt/QInt/QShort/QDouble/QTime | number |
| QUInt64/QInt64 | bigint |
| QBool | boolean |
| QList | Array |
| QStringList | Array<string> |
| QByteArray | Uint8Array |
| QMap | Object |
| QUserType | Object |
| QDateTime | Date |
| QChar | string |
| QInvalid | undefined |
64-bit integers are read as
bigintand may be written asbigintornumber.
QUserType
QUserType covers Qt's user-defined types (QVariant::UserType). Register a
parser/serializer for each type by name before use.
import { types } from 'qtdatastream-web';
const { QUserType, Types } = types;
// Simple usertype backed by a single Qt type
QUserType.register('NetworkId', Types.INT);
// Structured usertype (parsing order is preserved)
QUserType.register('BufferInfo', [
{ id: Types.INT },
{ network: Types.INT },
{ type: Types.SHORT },
{ name: Types.BYTEARRAY },
]);
// Usertypes may nest other usertypes by name (declare the referenced one first)
QUserType.register('BufferInfoContainer', [
{ id: Types.INT },
{ bufferInfo: 'BufferInfo' },
]);
Writing a usertype value:
import { write, types } from 'qtdatastream-web';
const bytes = write({
BufferInfo: new types.QUserType('BufferInfo', {
id: 2,
network: 4,
type: 5,
name: 'BufferInfo name',
}),
});
Decorators
Classes can declare their serialization shape with decorators. These use the
legacy decorator syntax, so enable experimentalDecorators in your
tsconfig.json (or the equivalent Babel plugin).
import { types, Serializable, serialize } from 'qtdatastream-web';
const { QString, QUInt, Types, QUserType } = types;
QUserType.register('Network::Server', Types.MAP);
@Serializable('Network::Server')
class Server {
@serialize(QString, { in: 'HostIn', out: 'HostOut' })
host?: string;
@serialize(QUInt, 'Port')
port = 6667;
@serialize(QUInt)
sslVersion = 0;
constructor(args: Partial<Server>) {
Object.assign(this, args);
}
}
Serializable's argument (the usertype name) is optional; without it, instances
export as a QMap. If a serializable class implements _export(), its return
value is serialized instead of the instance's own attributes.
Development
yarn install
yarn build # compile to dist/
yarn typecheck # type-check without emitting
yarn test # run the test suite (Vitest)
yarn test:watch # run Vitest in watch mode
Credits
This project is based on the original node-qtdatastream by Joël Charles, whose work defines the QDataStream encoding implemented here.
License
MIT.