MessagePack
Description
Section titled “Description”This example demonstrates encoding and decoding MessagePack data, which is used for Algorand transaction encoding. Topics covered:
- encodeMsgpack() to serialize data to MessagePack format
- decodeMsgpack() to deserialize MessagePack bytes
- Encoding simple objects with various types (strings, numbers, arrays)
- Key sorting: encodeMsgpack() sorts keys alphabetically (canonical encoding)
- Map returns: decodeMsgpack() returns a Map by default for object data
- BigInt value encoding/decoding
- Uint8Array (bytes) encoding
- Size comparison: MessagePack vs JSON
Prerequisites
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example common/08-msgpack.ts/** * Example: MessagePack * * This example demonstrates encoding and decoding MessagePack data, * which is used for Algorand transaction encoding. * * Topics covered: * - encodeMsgpack() to serialize data to MessagePack format * - decodeMsgpack() to deserialize MessagePack bytes * - Encoding simple objects with various types (strings, numbers, arrays) * - Key sorting: encodeMsgpack() sorts keys alphabetically (canonical encoding) * - Map returns: decodeMsgpack() returns a Map by default for object data * - BigInt value encoding/decoding * - Uint8Array (bytes) encoding * - Size comparison: MessagePack vs JSON * * Prerequisites: * - No LocalNet required */
import { arrayEqual, decodeMsgpack, encodeMsgpack, stringifyJson,} from '@algorandfoundation/algokit-utils/common';import { formatBytes, formatHex, printHeader, printInfo, printStep, printSuccess,} from '../shared/utils.js';
// Helper to find a value in a Map where keys are Uint8Arrayfunction getByStringKey(map: Map<number | bigint | Uint8Array, unknown>, keyStr: string): unknown { const keyBytes = new TextEncoder().encode(keyStr); for (const [k, v] of map) { if (k instanceof Uint8Array && arrayEqual(k, keyBytes)) { return v; } } return undefined;}
// Helper to convert Uint8Array key to string for displayfunction keyToString(key: number | bigint | Uint8Array): string { if (key instanceof Uint8Array) { return new TextDecoder().decode(key); } return String(key);}
// ============================================================================// Main Example// ============================================================================
printHeader('MessagePack Example');
// ============================================================================// Step 1: Basic MessagePack Encoding// ============================================================================printStep(1, 'Basic MessagePack Encoding');
printInfo('MessagePack is a binary serialization format used by Algorand');printInfo('encodeMsgpack() converts JavaScript objects to compact binary bytes');printInfo('');
const simpleObject = { name: 'Alice', balance: 1000, active: true,};
const encoded = encodeMsgpack(simpleObject);printInfo('Input object:');printInfo(` { name: 'Alice', balance: 1000, active: true }`);printInfo('');printInfo(`Encoded MessagePack (${encoded.length} bytes):`);printInfo(` ${formatHex(encoded)}`);printInfo(` Raw bytes: ${formatBytes(encoded)}`);printSuccess('Object encoded to MessagePack binary format');
// ============================================================================// Step 2: MessagePack Decoding - Returns a Map// ============================================================================printStep(2, 'MessagePack Decoding - Returns a Map');
printInfo('decodeMsgpack() decodes binary bytes back to data');printInfo('IMPORTANT: By default, objects are returned as Map (not plain objects)');printInfo('Keys are Uint8Array by default (for Algorand binary key support)');printInfo('');
const decoded = decodeMsgpack(encoded);
printInfo(`Decoded type: ${decoded.constructor.name}`);printInfo(`Is Map: ${decoded instanceof Map}`);printInfo('');printInfo('Decoded Map entries:');for (const [key, value] of decoded) { const keyStr = keyToString(key); const valueStr = value instanceof Uint8Array ? `"${new TextDecoder().decode(value)}"` : String(value); printInfo( ` "${keyStr}" => ${valueStr} (${value instanceof Uint8Array ? 'Uint8Array' : typeof value})`, );}printInfo('');
// Access values using helperprintInfo('Accessing values from decoded Map:');printInfo(` name: ${new TextDecoder().decode(getByStringKey(decoded, 'name') as Uint8Array)}`);printInfo(` balance: ${getByStringKey(decoded, 'balance')}`);printInfo(` active: ${getByStringKey(decoded, 'active')}`);printInfo('');printSuccess('decodeMsgpack() returns Map with Uint8Array keys for object data');
// ============================================================================// Step 3: Key Sorting (Canonical Encoding)// ============================================================================printStep(3, 'Key Sorting (Canonical Encoding)');
printInfo('encodeMsgpack() sorts keys alphabetically for canonical encoding');printInfo('This ensures consistent encoding regardless of property order');printInfo('');
// Create object with keys in non-alphabetical orderconst unorderedObject = { zebra: 3, apple: 1, mango: 2,};
printInfo('Input object (keys in non-alphabetical order):');printInfo(' { zebra: 3, apple: 1, mango: 2 }');printInfo('');
const encodedUnordered = encodeMsgpack(unorderedObject);const decodedUnordered = decodeMsgpack(encodedUnordered);
printInfo('Decoded Map key order:');const keys = Array.from(decodedUnordered.keys() as Iterable<number | bigint | Uint8Array>).map(k => keyToString(k),);printInfo(` Keys: [${keys.map(k => `"${k}"`).join(', ')}]`);printInfo('');
// Verify alphabetical orderconst isAlphabetical = keys.every((key, i) => i === 0 || keys[i - 1] <= key);if (isAlphabetical) { printSuccess('Keys are encoded in alphabetical order (canonical)');}
// Show that different input orders produce same encodingconst reorderedObject = { apple: 1, mango: 2, zebra: 3,};const encodedReordered = encodeMsgpack(reorderedObject);
printInfo('');printInfo('Same data, different key order in source:');printInfo(` Original encoding: ${formatHex(encodedUnordered)}`);printInfo(` Reordered encoding: ${formatHex(encodedReordered)}`);
const encodingsMatch = arrayEqual(encodedUnordered, encodedReordered);if (encodingsMatch) { printSuccess('Identical encoding regardless of source key order');}
// ============================================================================// Step 4: Encoding Various Types// ============================================================================printStep(4, 'Encoding Various Types');
printInfo('MessagePack supports various JavaScript types:');printInfo('');
// Stringsconst stringData = { message: 'Hello, Algorand!' };const encodedString = encodeMsgpack(stringData);printInfo(`String: "Hello, Algorand!" → ${encodedString.length} bytes`);
// Numbersconst numberData = { small: 42, medium: 1000000, large: 4294967295 };const encodedNumbers = encodeMsgpack(numberData);printInfo( `Numbers: { small: 42, medium: 1000000, large: 4294967295 } → ${encodedNumbers.length} bytes`,);
// Booleanconst boolData = { enabled: true, disabled: false };const encodedBool = encodeMsgpack(boolData);printInfo(`Boolean: { enabled: true, disabled: false } → ${encodedBool.length} bytes`);
// Arrayconst arrayData = { items: [1, 2, 3, 'four', true] };const encodedArray = encodeMsgpack(arrayData);printInfo(`Array: { items: [1, 2, 3, 'four', true] } → ${encodedArray.length} bytes`);
// Nullconst nullData = { value: null };const encodedNull = encodeMsgpack(nullData);printInfo(`Null: { value: null } → ${encodedNull.length} bytes`);
// Nested objectconst nestedData = { level1: { level2: { value: 'deep', }, },};const encodedNested = encodeMsgpack(nestedData);printInfo(`Nested: { level1: { level2: { value: 'deep' } } } → ${encodedNested.length} bytes`);
printInfo('');printSuccess('All common JavaScript types encoded successfully');
// ============================================================================// Step 5: BigInt Value Encoding/Decoding// ============================================================================printStep(5, 'BigInt Value Encoding/Decoding');
printInfo('MessagePack can encode BigInt values for large numbers');printInfo('This is essential for Algorand which uses uint64 values');printInfo('');
// Values that exceed Number.MAX_SAFE_INTEGERconst bigIntData = { normalNumber: 1000000, bigNumber: 9007199254740993n, // MAX_SAFE_INTEGER + 2 maxUint64: 18446744073709551615n, // 2^64 - 1};
printInfo('Input with BigInt values:');printInfo(` normalNumber: ${bigIntData.normalNumber}`);printInfo(` bigNumber: ${bigIntData.bigNumber}n (> MAX_SAFE_INTEGER)`);printInfo(` maxUint64: ${bigIntData.maxUint64}n (2^64 - 1)`);printInfo('');
const encodedBigInt = encodeMsgpack(bigIntData);printInfo(`Encoded to ${encodedBigInt.length} bytes:`);printInfo(` ${formatHex(encodedBigInt)}`);printInfo('');
const decodedBigInt = decodeMsgpack(encodedBigInt);const normalNum = getByStringKey(decodedBigInt, 'normalNumber');const bigNum = getByStringKey(decodedBigInt, 'bigNumber');const maxU64 = getByStringKey(decodedBigInt, 'maxUint64');
printInfo('Decoded values:');printInfo(` normalNumber: ${normalNum} (type: ${typeof normalNum})`);printInfo(` bigNumber: ${bigNum} (type: ${typeof bigNum})`);printInfo(` maxUint64: ${maxU64} (type: ${typeof maxU64})`);printInfo('');
// Verify bigint preservationif (maxU64 === bigIntData.maxUint64) { printSuccess('BigInt values preserved through encode/decode cycle');}
// ============================================================================// Step 6: Uint8Array (Bytes) Encoding// ============================================================================printStep(6, 'Uint8Array (Bytes) Encoding');
printInfo('Algorand uses Uint8Array for binary data (addresses, keys, etc.)');printInfo('MessagePack has native support for binary (bytes) type');printInfo('');
// Create sample byte arraysconst sampleBytes = new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]);const addressBytes = new Uint8Array(32).fill(0xab); // Simulated 32-byte public key
const bytesData = { shortBytes: sampleBytes, addressKey: addressBytes,};
printInfo('Input with Uint8Array values:');printInfo(` shortBytes: ${formatHex(sampleBytes)} (${sampleBytes.length} bytes)`);printInfo( ` addressKey: ${formatHex(addressBytes.slice(0, 8))}... (${addressBytes.length} bytes)`,);printInfo('');
const encodedBytes = encodeMsgpack(bytesData);printInfo(`Encoded to ${encodedBytes.length} bytes`);printInfo('');
const decodedBytes = decodeMsgpack(encodedBytes);const decodedShort = getByStringKey(decodedBytes, 'shortBytes') as Uint8Array;const decodedAddress = getByStringKey(decodedBytes, 'addressKey') as Uint8Array;
printInfo('Decoded values:');printInfo(` shortBytes: ${formatHex(decodedShort)} (${decodedShort.length} bytes)`);printInfo( ` addressKey: ${formatHex(decodedAddress.slice(0, 8))}... (${decodedAddress.length} bytes)`,);printInfo('');
// Verify bytes matchconst bytesMatch = arrayEqual(sampleBytes, decodedShort);if (bytesMatch) { printSuccess('Uint8Array values preserved through encode/decode cycle');}
// ============================================================================// Step 7: MessagePack vs JSON Size Comparison// ============================================================================printStep(7, 'MessagePack vs JSON Size Comparison');
printInfo('MessagePack typically produces smaller output than JSON');printInfo('');
// Test data representative of Algorand transaction fieldsconst transactionLike = { type: 'pay', sender: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ', receiver: 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAR7CWY', amount: 1000000, fee: 1000, firstValid: 10000000, lastValid: 10001000, note: 'Payment for services',};
const msgpackSize = encodeMsgpack(transactionLike).length;const jsonSize = stringifyJson(transactionLike).length;
printInfo('Transaction-like data:');printInfo(` MessagePack size: ${msgpackSize} bytes`);printInfo(` JSON size: ${jsonSize} bytes`);printInfo( ` Space saved: ${jsonSize - msgpackSize} bytes (${((1 - msgpackSize / jsonSize) * 100).toFixed(1)}%)`,);printInfo('');
// Another comparison with numeric dataconst numericData = { values: [1, 10, 100, 1000, 10000, 100000, 1000000], metadata: { count: 7, sum: 1111111 },};
const numericMsgpack = encodeMsgpack(numericData).length;const numericJson = stringifyJson(numericData).length;
printInfo('Numeric-heavy data:');printInfo(` MessagePack size: ${numericMsgpack} bytes`);printInfo(` JSON size: ${numericJson} bytes`);printInfo( ` Space saved: ${numericJson - numericMsgpack} bytes (${((1 - numericMsgpack / numericJson) * 100).toFixed(1)}%)`,);printInfo('');
printSuccess('MessagePack provides significant space savings over JSON');
// ============================================================================// Step 8: Working with Decoded Map Data// ============================================================================printStep(8, 'Working with Decoded Map Data');
printInfo('Since decodeMsgpack() returns a Map with Uint8Array keys,');printInfo('you need to match keys by byte content, not reference.');printInfo('');
const sampleData = { name: 'TestTx', amount: 5000000n, tags: ['transfer', 'urgent'],};
const encodedSample = encodeMsgpack(sampleData);const decodedMap = decodeMsgpack(encodedSample);
printInfo('Getting individual values (using helper):');printInfo( ` name: ${new TextDecoder().decode(getByStringKey(decodedMap, 'name') as Uint8Array)}`,);printInfo(` amount: ${getByStringKey(decodedMap, 'amount')}`);const tags = getByStringKey(decodedMap, 'tags') as Uint8Array[];const tagStrings = tags.map(t => new TextDecoder().decode(t));printInfo(` tags: [${tagStrings.join(', ')}]`);printInfo('');
printInfo('Iterating over entries:');for (const [key, value] of decodedMap) { const keyStr = keyToString(key); let valueStr: string; if (value instanceof Uint8Array) { valueStr = `"${new TextDecoder().decode(value)}"`; } else if (Array.isArray(value)) { const items = value.map(v => v instanceof Uint8Array ? new TextDecoder().decode(v) : String(v), ); valueStr = `[${items.join(', ')}]`; } else { valueStr = String(value); } printInfo(` ${keyStr}: ${valueStr}`);}printInfo('');
printSuccess('Map provides flexible data access patterns');
// ============================================================================// Summary// ============================================================================printStep(9, 'Summary');
printInfo('MessagePack encoding/decoding for Algorand:');printInfo('');printInfo(' encodeMsgpack(data):');printInfo(' - Serializes JavaScript objects to binary MessagePack');printInfo(' - Keys are sorted alphabetically (canonical encoding)');printInfo(' - Supports strings, numbers, BigInt, Uint8Array, arrays, nested objects');printInfo(' - Undefined values are ignored');printInfo('');printInfo(' decodeMsgpack(bytes):');printInfo(' - Deserializes MessagePack bytes to JavaScript');printInfo(' - Returns Map by default (not plain object)');printInfo(' - Keys are Uint8Array by default (rawBinaryStringKeys: true)');printInfo(' - Preserves BigInt for large integers');printInfo(' - Preserves Uint8Array for binary data');printInfo('');printInfo(' Use cases:');printInfo(' - Algorand transaction encoding');printInfo(' - Compact data serialization');printInfo(' - Deterministic encoding (same data = same bytes)');printInfo('');printSuccess('MessagePack Example completed!');