Skip to content

Notes on number representation for serialization and deserialization

Saúl Cabrera edited this page Sep 23, 2021 · 2 revisions

Relationship between MessagePack and JavaScript values

Here's a table that maps the msgpack specification to the different data types in Javascript.

MessagePack Value JavaScript Value
positive fixint number
fixmap object
fixarray array
fixstr string
nil null
false false
true true
bin 8 not supported
bin 16 not supported
bin 32 not supported
float 32 number
float 64 number
uint 8 number
uint 16 number
uint 32 number
uint 64 number/bigint
int 8 number
int 16 number
int 32 number
int 64 number/bigint
str 8 string
str 16 string
str 32 string
map 16 object
map 32 object
negative fixint number

Relationship between Number and BigInt in JavaScript

Javascript originally didn't support i64/u64 values. In order to support these values, BigInt was added to the specification and is nowadays supported by most JavaScript engines. However BigInt can't naturally interact with other number types, all mathematical operations must be done at the BigInt level. To solve this the developer needs to make sure to check the type of the variable that they belive could be a BigInt, this doesn't apply to all numerical values and will vary on case by case.

For a more complete example, the following script would fail, if the proper checks are not done before comparing a BigInt with a regular JavaScript number:

var Shopify;
Shopify = {
  // Let's assume that productId was serialized as BigInt
  main: (productId) => {
    if (productId === 42)) { // will throw since the equality operation must be applied to two BigInts or Numbers
      return "ok";
    }

    throw new Error("i is not BigInt(42)");
  }
}

In order to make the previous script succeed, we need to check the type of productId and convert it to the corresponding type before doing any operations with it.

var Shopify;
Shopify = {
  // Let's assume that productId was serialized as BigInt
  main: (productId) => {
    var ty = typeof productId;
    switch (ty) {
      case 'number':
         // ...
      case 'bigint':
        // ...
    }
  }
}

Other notes

  • Rust's MessagePack crate serializes i64 that are larger than an i32 as a u64.
  • Rust's MessagePack crate serializes to i64 only when values are < i32::MIN.
  • Rust's MessagePack crate always tries to serialize to the smallest format. See previous section...
  • QuickJS optimizes values between i32::MIN to i32::MAX by storing them as Int32 instead of Float64
  • During deserialization, BigInts are not clamped to MIN/MAX, they instead return an error when they overflow/underflow.