All files / util/src/json-size jsonSizeFast.ts

100% Statements 23/23
100% Branches 6/6
100% Functions 3/3
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 622x 2x 8x 2x     2x 4x 4x 9x       9x 4x                                                                   2x 28x 25x   6x   7x   6x   6x 4x    
const arraySize = (arr: unknown[]): number => {
  let size = 2;
  for (let i = arr.length - 1; i >= 0; i--) size += jsonSizeFast(arr[i]);
  return size;
};
 
const objectSize = (obj: Record<string, unknown>): number => {
  let size = 2;
  for (const key in obj)
    if (
      // biome-ignore lint: .hasOwnProperty access is intentional
      obj.hasOwnProperty(key)
    )
      size += 2 + key.length + jsonSizeFast(obj[key]);
  return size;
};
 
/**
 * This function is the fastest way to approximate size of JSON object in bytes.
 *
 * It uses the following heuristics:
 *
 * - Boolean: 1 byte.
 * - Null: 1 byte.
 * - Number: 9 bytes (1 byte to store the number type, 8 bytes to store the number).
 * - String: 4 bytes + string length. String length is encoded in UTF-8, so it is not
 *   exactly the same as the number of bytes in the string.
 * - Array: 2 bytes + sum of sizes of elements.
 * - Object: 2 bytes + 2 bytes for each key + length of each key + sum of sizes of values.
 *
 * Rationale:
 *
 * - Booleans and `null` are stored as one byte in MessagePack.
 * - Maximum size of a number in MessagePack is 9 bytes (1 byte for the type,
 *   8 bytes for the number).
 * - Maximum overhead for string storage is 4 bytes in MessagePack. We use that, especially
 *   because we approximate the size of strings in UTF-8, which can consume more bytes if
 *   non-ASCII characters are present.
 * - Maximum overhead for arrays is 4 bytes in MessagePack, but we use 2 bytes for the
 *   array length, as we don't expect most arrays to be longer than 65,535 elements.
 * - Maximum overhead for objects is 4 bytes in MessagePack, but we use 2 bytes for the
 *   object length, as we don't expect most objects to have more than 65,535 keys.
 * - For object keys we use 2 bytes overhead for each key, as we don't expect most
 *   keys to be longer than 65,535 characters.
 *
 * @param value JSON value to calculate approximate size of
 * @returns Number of bytes required to store the JSON value
 */
export const jsonSizeFast = (value: unknown): number => {
  if (value === null) return 1;
  switch (typeof value) {
    case 'number':
      return 9;
    case 'string':
      return 4 + value.length;
    case 'boolean':
      return 1;
  }
  if (value instanceof Array) return arraySize(value);
  return objectSize(value as Record<string, unknown>);
};