Type System

Crous maps JavaScript types to its internal binary type system. Understanding these mappings is essential for predictable serialization, especially for edge cases around numbers and types that don't exist natively in JavaScript.

Type Mapping Table

JavaScript TypeCrous Wire TypeDecoded AsNotes
nullNULL (0x01)null
undefinedNULL (0x01)nullLossy — becomes null
true / falseBOOL (0x02)boolean
number (integer)INT64 (0x03)numberHeuristic applied
number (float)FLOAT64 (0x04)number
stringSTRING (0x05)stringUTF-8 encoded
BufferBYTES (0x06)Buffer
Uint8ArrayBYTES (0x06)BufferDecoded as Buffer
ArrayLIST (0x07)ArrayHeterogeneous OK
Object (plain)DICT (0x09)ObjectString keys only
SetTAGGED(90) → LISTSetVia tagged type

Number Encoding Heuristic

JavaScript has a single number type (IEEE 754 double). Crous uses a heuristic to determine whether a number should be encoded as an integer or float:

number_heuristic.c
// Internal C logic (simplified)
double num = napi_get_value_double(value);
int64_t as_int = (int64_t)num;

if ((double)as_int == num &&
    as_int >= INT64_MIN &&
    as_int <= INT64_MAX) {
    // Encode as INT64
} else {
    // Encode as FLOAT64
}

In practice, this means:

numbers.js
const crous = require('crous');

// These encode as INT64
crous.dumps(42);           // integer
crous.dumps(0);            // zero
crous.dumps(-100);         // negative integer
crous.dumps(2 ** 53 - 1);  // max safe integer

// These encode as FLOAT64
crous.dumps(3.14);         // has decimal
crous.dumps(NaN);          // NaN
crous.dumps(Infinity);     // Infinity
crous.dumps(-Infinity);    // -Infinity
crous.dumps(1.0);          // ⚠️ This is INT64! (1.0 === 1 in JS)

1.0 is an Integer

In JavaScript, 1.0 === 1 is true. There's no way to distinguish them. Crous encodes 1.0 as INT64 because the heuristic sees a whole number. This is generally not a problem, but be aware of it.

Null and Undefined

Both null and undefined map to the same wire type (NULL). On decode, they always come back as null.

null_undefined.js
const crous = require('crous');

const data = {
    a: null,
    b: undefined
};

const result = crous.loads(crous.dumps(data));
console.log(result);
// { a: null, b: null }
// undefined was converted to null!

Lossy Mapping

undefinednull is a lossy conversion. If you need to preserve the distinction, filter out undefined values before serialization or use a sentinel value.

Strings

Strings are encoded as UTF-8 with a 4-byte length prefix. There is no practical length limit beyond available memory.

strings.js
const crous = require('crous');

// Regular strings
crous.dumps('Hello, World!');

// Unicode — full support
crous.dumps('こんにちは 🌍');

// Empty strings
crous.dumps('');

// Very long strings — works fine
crous.dumps('x'.repeat(1_000_000));

Binary Data (Buffer)

Crous natively supports binary data through Buffer. No base64 encoding needed — bytes go straight into the wire format.

binary.js
const crous = require('crous');
const fs = require('fs');

// Buffer from array
const buf = Buffer.from([0x89, 0x50, 0x4E, 0x47]);
crous.dumps(buf);

// Read a file as binary
const image = fs.readFileSync('photo.jpg');
const encoded = crous.dumps({ image });
// The image bytes are stored efficiently — no base64 bloat!

// Uint8Array also works
const arr = new Uint8Array([1, 2, 3, 4]);
crous.dumps(arr);  // encoded as BYTES

Arrays

JavaScript arrays map to Crous LIST. They can contain any mix of types.

arrays.js
const crous = require('crous');

// Homogeneous
crous.dumps([1, 2, 3, 4, 5]);

// Heterogeneous — no problem
crous.dumps([1, 'two', true, null, Buffer.from([3])]);

// Nested
crous.dumps([[1, 2], [3, 4], [5, 6]]);

// Empty
crous.dumps([]);

No Tuples

JavaScript doesn't have a tuple type. Python tuples deserialized by the Node.js SDK will become Arrays. This is a one-way lossy mapping when interoperating with the Python SDK.

Objects (Dictionaries)

Plain objects are encoded as Crous DICT. Keys must be strings.

objects.js
const crous = require('crous');

// Plain objects
crous.dumps({ name: 'Alice', age: 30 });

// Nested objects
crous.dumps({
    user: {
        profile: {
            bio: 'Hello!'
        }
    }
});

// Empty object
crous.dumps({});

// ⚠️ Non-string keys will throw CrousEncodeError
// crous.dumps({ [Symbol()]: 'value' }); // Error!

Sets

JavaScript Set objects are serialized using the tagged type mechanism with tag 90. The set elements are stored as a list internally.

sets.js
const crous = require('crous');

const data = {
    tags: new Set(['admin', 'user', 'moderator']),
    ids: new Set([1, 2, 3])
};

const result = crous.loads(crous.dumps(data));
console.log(result.tags instanceof Set); // true
console.log(result.tags.has('admin'));    // true

Cross-SDK Type Compatibility

When exchanging Crous data between the Python and Node.js SDKs, be aware of these type differences:

Python TypeWire TypeJavaScript TypeLossy?
NoneNULLnullNo
boolBOOLbooleanNo
intINT64number⚠️ > 2⁵³
floatFLOAT64numberNo
strSTRINGstringNo
bytesBYTESBufferNo
listLISTArrayNo
tupleTUPLEArrayYes
dictDICTObjectNo
setSETSetNo
frozensetFROZENSETSetYes

Large Integers

Python can serialize integers beyond JavaScript's safe integer range (2⁵³ - 1). These will lose precision when decoded by the Node.js SDK. Consider using strings for very large integers in cross-language scenarios.