Skip to content

MessagePack

← Back to Common Utilities

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
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example common/08-msgpack.ts

View source on GitHub

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 Uint8Array
function 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 display
function 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 helper
printInfo('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 order
const 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 order
const 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 encoding
const 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('')
// Strings
const stringData = { message: 'Hello, Algorand!' }
const encodedString = encodeMsgpack(stringData)
printInfo(`String: "Hello, Algorand!" → ${encodedString.length} bytes`)
// Numbers
const numberData = { small: 42, medium: 1000000, large: 4294967295 }
const encodedNumbers = encodeMsgpack(numberData)
printInfo(`Numbers: { small: 42, medium: 1000000, large: 4294967295 } → ${encodedNumbers.length} bytes`)
// Boolean
const boolData = { enabled: true, disabled: false }
const encodedBool = encodeMsgpack(boolData)
printInfo(`Boolean: { enabled: true, disabled: false } → ${encodedBool.length} bytes`)
// Array
const arrayData = { items: [1, 2, 3, 'four', true] }
const encodedArray = encodeMsgpack(arrayData)
printInfo(`Array: { items: [1, 2, 3, 'four', true] } → ${encodedArray.length} bytes`)
// Null
const nullData = { value: null }
const encodedNull = encodeMsgpack(nullData)
printInfo(`Null: { value: null } → ${encodedNull.length} bytes`)
// Nested object
const 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_INTEGER
const 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 preservation
if (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 arrays
const 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 match
const 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 fields
const 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 data
const 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!')