NPM

Build Status License: MIT

Javascript QDatastream (de)serializer.

List of types handled for (de)serialization: QBool, QShort, QInt, QInt64, QUInt, QUInt64, QDouble, QMap, QList, QString, QVariant, QStringList, QByteArray, QUserType, QDateTime, QTime, QChar, QInvalid

Getting Started

Install the module with npm install node-qtdatastream --production, or npm install node-qtdatastream for development purpose.

Documentation

Technical documentation

Type inference

Javascript types can be automatically converted to Qt Types, and here is the default behavior

javascript to QClass

javascript QClass
string QString
number QUInt (this can be overloaded)
boolean QBool
Array QList<QVariant<?>>
Date QDateTime
Map QMap<QString, QVariant<?>>
Object QMap<QString, QVariant<?>>

You can always force any type to be coerced to any Qt type

const { QByteArray } = require('qtdatastream').types;
const s = "hello"; // If given to the writer, it will be coerced to QString
const qbytearray = QByteArray.from(s); // This will write the same string but as a QByteArray

NB: you can change default behavior for number

const { QVariant, Types } = require('qtdatastream').types;
const n = 1; // Would be written as QUInt
QVariant.coerceNumbersTo(Types.DOUBLE); // Will now write any number as QDouble

QClass to javascript

Qt Types are also converted to native javascript type automatically upon reading

QClass javascript
QString string
QUInt number
QInt number
QUInt64 number
QInt64 number
QDouble number
QShort number
QBool number
QList Array
QStringList Array<string>
QByteArray Buffer
QMap Object
QUserType Object
QDateTime Date
QTime number
QChar string
QInvalid undefined

QUserType special treatment

QUserType are special types defined by user (QVariant::UserType).

QUserType are defined like this <size:uint32><bytearray of size>. bytearray can be casted to string (but it is not a string as intended by Qt, because it is UTF8 and not UTF16) : bytearray.toString(). The resulting string is the QUserType key.

Reader

The Reader use an internal mechanism to know which parser must be used for each QUserType. They are defined like this:

const { QUserType } = require('qtdatastream').types;
QUserType.register("NetworkId", qtdatastream.Types.INT); //NetworkId here is our key

This tell the reader to decode NetworkId bytearray like and INT. But those structures can be much more complicated:

const { QUserType } = require('qtdatastream').types;
QUserType.register("BufferInfo", [
    {id: qtdatastream.Types.INT},
    {network: qtdatastream.Types.INT},
    {type: qtdatastream.Types.SHORT},
    {group: qtdatastream.Types.INT},
    {name: qtdatastream.Types.BYTEARRAY}
]);

The bytearray corresponding to this structure look like this :

  <int32><int32><int16><int32><qbytearray>

The whole new type will be put in a new Object, the id key will contain the first <int32>, the network key will contain the second <int32>, etc. The definition is contained into an array to force a parsing order (here, id will always be the first <int32> block).

UserTypes can also be nested, by specifying the usertype name instead of Qt type :

QUserType.register("BufferInfoContainer", [
    {id: qtdatastream.Types.INT},
    {bufferInfo: "BufferInfo"} // here we reference the BufferInfo QUserType
]);

Keep in mind that if a usertype X references usertype Y, Y should be declared before X.

Writer

Custom usertypes can be defined as for Reader, with the help of QUserType.register method.

Writing UserType is done as follow:

const { Socket } = require('qtdatastream').socket;
const { QUserType } = require('qtdatastream').types;
const qtsocket = new Socket(myRealSocket);

const data = {
    "BufferInfo": new QUserType("BufferInfo", {
        id: 2,
        network: 4,
        type: 5,
        group: 1,
        name: "BufferInfo name"
    })
});

qtsocket.write(data);

Some more examples can be found in test folder.

ES6/7

ES7 decorators can be used to simplify serializable data representation

const { types: { QUserType, QString, QUInt, Types }, serialization: { Serializable, serialize } } = require('qtdatastream');
// Register usertype
QUserType.register('Network::Server', Types.MAP);

@Serializable('Network::Server')
export class Server {
    @serialize(QString, {in: 'HostIn', out: 'HostOut'))
    host;

    @serialize(QUInt, 'Port')
    port = 6667;

    @serialize(QUInt)
    sslVersion = 0;

    constructor(args) {
        this.blob = true; // will not be serialized at export
        Object.assign(this, args);
    }
}

const parsedUserType = {
    HostIn: 'myHost',
    Port: 1234
}; // This usually comes from qtsocket
const server = new Server(parsedUserType);
// server == {
//     host: 'myHost',
//     port: 1234,
//     sslVersion: 0
// }

// This will call server.export() method before sending to the server,
// which exports the object as dictated by Server class and 'Network::Server' usertype
qtsocket.write(server)

Serializable parameter (usertype) is optionnal. If unspecified, it will be exported as a QMap.

If Serializable class implements _export method, the return of this function will be used instead of object own attributes.

@Serializable()
export class Server {
  _export() {
    return {
      'a': 'b'
    };
  }
}

Example

const { Socket } = require('qtdatastream').socket;
const { QUserType } = require('qtdatastream').types;
const net = require('net');

var client = net.Socket();

// Connect to a Qt socket
// and write something into the socket
client.connect(65000, "domain.tld", function(){
    const qtsocket = new Socket(client);

    // Here data is the already parsed response
    qtsocket.on('data', function(data) {
        //...
    });

    // Write something to the socket
    qtsocket.write({
        "AString": "BString",
        "CString": 42
    });
});

Debugging

Debug mode can be activated by setting environment variable DEBUG in your shell before launching your program:

export DEBUG="qtdatastream:*"

License

Copyright (c) 2017 Joël Charles Licensed under the MIT license.