Primitive Codecs
Description
Section titled “Description”This example demonstrates how to use primitive codec types for encoding/decoding basic values in wire format (JSON or MessagePack). Topics covered:
- Codec interface: encode(), decode(), defaultValue() methods
- numberCodec for encoding/decoding numbers
- bigIntCodec for encoding/decoding BigInt values
- booleanCodec for encoding/decoding boolean values
- stringCodec for encoding/decoding UTF-8 strings
- bytesCodec for encoding/decoding raw bytes
- bytesBase64Codec for base64 encoding
- addressCodec for encoding/decoding Algorand addresses
- Round-trip verification: decode(encode(value)) equals original
Prerequisites
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example common/09-primitive-codecs.ts/** * Example: Primitive Codecs * * This example demonstrates how to use primitive codec types for encoding/decoding * basic values in wire format (JSON or MessagePack). * * Topics covered: * - Codec interface: encode(), decode(), defaultValue() methods * - numberCodec for encoding/decoding numbers * - bigIntCodec for encoding/decoding BigInt values * - booleanCodec for encoding/decoding boolean values * - stringCodec for encoding/decoding UTF-8 strings * - bytesCodec for encoding/decoding raw bytes * - bytesBase64Codec for base64 encoding * - addressCodec for encoding/decoding Algorand addresses * - Round-trip verification: decode(encode(value)) equals original * * Prerequisites: * - No LocalNet required */
import type { EncodingFormat } from '@algorandfoundation/algokit-utils/common';import { Address, addressCodec, arrayEqual, bigIntCodec, booleanCodec, bytesBase64Codec, bytesCodec, numberCodec, stringCodec,} from '@algorandfoundation/algokit-utils/common';import { formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js';
// ============================================================================// Main Example// ============================================================================
printHeader('Primitive Codecs Example');
// ============================================================================// Step 1: The Codec Interface// ============================================================================printStep(1, 'The Codec Interface');
printInfo('Codecs provide bidirectional transformation between app types and wire formats.');printInfo('Each codec has these core methods:');printInfo('');printInfo(' encode(value, format) - Transform app value to wire format');printInfo(' decode(value, format) - Transform wire value to app value');printInfo(' defaultValue() - Get the default value for this type');printInfo('');printInfo('Format can be "json" or "msgpack" - some codecs behave differently per format.');printInfo('');
// Show default values for each codecprintInfo('Default values for each primitive codec:');printInfo(` numberCodec.defaultValue() = ${numberCodec.defaultValue()}`);printInfo(` bigIntCodec.defaultValue() = ${bigIntCodec.defaultValue()}n`);printInfo(` booleanCodec.defaultValue() = ${booleanCodec.defaultValue()}`);printInfo(` stringCodec.defaultValue() = "${stringCodec.defaultValue()}" (empty string)`);printInfo( ` bytesCodec.defaultValue() = Uint8Array(${bytesCodec.defaultValue().length}) (empty)`,);printInfo( ` addressCodec.defaultValue() = ${addressCodec.defaultValue().toString().slice(0, 10)}... (zero address)`,);printInfo('');printSuccess('Codec interface provides consistent encode/decode methods');
// ============================================================================// Step 2: numberCodec - Encoding/Decoding Numbers// ============================================================================printStep(2, 'numberCodec - Encoding/Decoding Numbers');
printInfo('numberCodec handles JavaScript number values.');printInfo('Numbers pass through unchanged in both JSON and msgpack formats.');printInfo('');
const testNumbers = [0, 42, -100, 3.14159, Number.MAX_SAFE_INTEGER];
for (const num of testNumbers) { const encodedJson = numberCodec.encode(num, 'json'); const encodedMsgpack = numberCodec.encode(num, 'msgpack'); const decodedJson = numberCodec.decode(encodedJson, 'json'); const decodedMsgpack = numberCodec.decode(encodedMsgpack, 'msgpack');
printInfo(` ${num}:`); printInfo( ` JSON: encode=${encodedJson}, decode=${decodedJson}, matches=${num === decodedJson}`, ); printInfo( ` msgpack: encode=${encodedMsgpack}, decode=${decodedMsgpack}, matches=${num === decodedMsgpack}`, );}printInfo('');
// Round-trip verificationconst roundTripNum = 12345;const rtEncoded = numberCodec.encode(roundTripNum, 'json');const rtDecoded = numberCodec.decode(rtEncoded, 'json');if (rtDecoded === roundTripNum) { printSuccess(`Round-trip: decode(encode(${roundTripNum})) === ${rtDecoded}`);}
// ============================================================================// Step 3: bigIntCodec - Encoding/Decoding BigInt Values// ============================================================================printStep(3, 'bigIntCodec - Encoding/Decoding BigInt Values');
printInfo('bigIntCodec handles JavaScript BigInt values (uint64 on Algorand).');printInfo('Essential for values exceeding Number.MAX_SAFE_INTEGER (9007199254740991).');printInfo('');
const testBigInts: bigint[] = [0n, 100n, 9007199254740993n, 18446744073709551615n]; // 0, 100, MAX_SAFE+2, max uint64
for (const big of testBigInts) { const encodedJson = bigIntCodec.encode(big, 'json'); const encodedMsgpack = bigIntCodec.encode(big, 'msgpack'); const decodedJson = bigIntCodec.decode(encodedJson, 'json'); const decodedMsgpack = bigIntCodec.decode(encodedMsgpack, 'msgpack');
printInfo(` ${big}n:`); printInfo( ` JSON: encode=${encodedJson}${typeof encodedJson === 'bigint' ? 'n' : ''}, decode=${decodedJson}n, matches=${big === decodedJson}`, ); printInfo( ` msgpack: encode=${encodedMsgpack}${typeof encodedMsgpack === 'bigint' ? 'n' : ''}, decode=${decodedMsgpack}n, matches=${big === decodedMsgpack}`, );}printInfo('');
// Round-trip verification with large valueconst roundTripBig = 18446744073709551615n; // max uint64const rtBigEncoded = bigIntCodec.encode(roundTripBig, 'msgpack');const rtBigDecoded = bigIntCodec.decode(rtBigEncoded, 'msgpack');if (rtBigDecoded === roundTripBig) { printSuccess(`Round-trip: decode(encode(${roundTripBig}n)) === ${rtBigDecoded}n`);}
// ============================================================================// Step 4: booleanCodec - Encoding/Decoding Boolean Values// ============================================================================printStep(4, 'booleanCodec - Encoding/Decoding Boolean Values');
printInfo('booleanCodec handles true/false values.');printInfo('Default value is false.');printInfo('');
const testBools = [true, false];
for (const bool of testBools) { const encodedJson = booleanCodec.encode(bool, 'json'); const encodedMsgpack = booleanCodec.encode(bool, 'msgpack'); const decodedJson = booleanCodec.decode(encodedJson, 'json'); const decodedMsgpack = booleanCodec.decode(encodedMsgpack, 'msgpack');
printInfo(` ${bool}:`); printInfo( ` JSON: encode=${encodedJson}, decode=${decodedJson}, matches=${bool === decodedJson}`, ); printInfo( ` msgpack: encode=${encodedMsgpack}, decode=${decodedMsgpack}, matches=${bool === decodedMsgpack}`, );}printInfo('');
// Round-trip verificationconst roundTripBool = true;const rtBoolEncoded = booleanCodec.encode(roundTripBool, 'json');const rtBoolDecoded = booleanCodec.decode(rtBoolEncoded, 'json');if (rtBoolDecoded === roundTripBool) { printSuccess(`Round-trip: decode(encode(${roundTripBool})) === ${rtBoolDecoded}`);}
// ============================================================================// Step 5: stringCodec - Encoding/Decoding UTF-8 Strings// ============================================================================printStep(5, 'stringCodec - Encoding/Decoding UTF-8 Strings');
printInfo('stringCodec handles UTF-8 string values.');printInfo('In msgpack, strings may be received as Uint8Array (normalized on decode).');printInfo('');
const testStrings = ['', 'Hello', 'Hello, Algorand!', 'Unicode: \u00e9\u00e8\u00ea'];
for (const str of testStrings) { const encodedJson = stringCodec.encode(str, 'json'); const encodedMsgpack = stringCodec.encode(str, 'msgpack'); const decodedJson = stringCodec.decode(encodedJson, 'json'); const decodedMsgpack = stringCodec.decode(encodedMsgpack, 'msgpack');
const displayStr = str === '' ? '(empty)' : `"${str}"`; printInfo(` ${displayStr}:`); printInfo( ` JSON: encode="${encodedJson}", decode="${decodedJson}", matches=${str === decodedJson}`, ); printInfo( ` msgpack: encode="${encodedMsgpack}", decode="${decodedMsgpack}", matches=${str === decodedMsgpack}`, );}printInfo('');
// Round-trip verificationconst roundTripStr = 'Algorand SDK';const rtStrEncoded = stringCodec.encode(roundTripStr, 'json');const rtStrDecoded = stringCodec.decode(rtStrEncoded, 'json');if (rtStrDecoded === roundTripStr) { printSuccess(`Round-trip: decode(encode("${roundTripStr}")) === "${rtStrDecoded}"`);}
// ============================================================================// Step 6: bytesCodec - Encoding/Decoding Raw Bytes// ============================================================================printStep(6, 'bytesCodec - Encoding/Decoding Raw Bytes');
printInfo('bytesCodec handles Uint8Array values (raw binary data).');printInfo('JSON format: encodes to base64 string');printInfo('msgpack format: encodes as raw bytes');printInfo('');
const testBytes = [ new Uint8Array([]), new Uint8Array([0x01, 0x02, 0x03]), new Uint8Array([0xde, 0xad, 0xbe, 0xef]), new Uint8Array(32).fill(0xab), // 32-byte key simulation];
for (const bytes of testBytes) { const encodedJson = bytesCodec.encode(bytes, 'json'); const encodedMsgpack = bytesCodec.encode(bytes, 'msgpack'); const decodedJson = bytesCodec.decode(encodedJson, 'json'); const decodedMsgpack = bytesCodec.decode(encodedMsgpack, 'msgpack');
const displayBytes = bytes.length === 0 ? '(empty)' : formatHex(bytes.slice(0, 8)) + (bytes.length > 8 ? '...' : ''); printInfo(` ${displayBytes} (${bytes.length} bytes):`); printInfo(` JSON: encode="${encodedJson}" (base64)`); printInfo( ` decode=${formatHex(decodedJson.slice(0, 8))}${decodedJson.length > 8 ? '...' : ''}, matches=${arrayEqual(bytes, decodedJson)}`, ); printInfo( ` msgpack: encode=${formatHex((encodedMsgpack as Uint8Array).slice(0, 8))}${(encodedMsgpack as Uint8Array).length > 8 ? '...' : ''}`, ); printInfo( ` decode=${formatHex(decodedMsgpack.slice(0, 8))}${decodedMsgpack.length > 8 ? '...' : ''}, matches=${arrayEqual(bytes, decodedMsgpack)}`, );}printInfo('');
// Round-trip verificationconst roundTripBytes = new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]);const rtBytesEncodedJson = bytesCodec.encode(roundTripBytes, 'json');const rtBytesDecodedJson = bytesCodec.decode(rtBytesEncodedJson, 'json');if (arrayEqual(rtBytesDecodedJson, roundTripBytes)) { printSuccess(`Round-trip (JSON): decode(encode(${formatHex(roundTripBytes)})) matches original`);}
const rtBytesEncodedMsgpack = bytesCodec.encode(roundTripBytes, 'msgpack');const rtBytesDecodedMsgpack = bytesCodec.decode(rtBytesEncodedMsgpack, 'msgpack');if (arrayEqual(rtBytesDecodedMsgpack, roundTripBytes)) { printSuccess( `Round-trip (msgpack): decode(encode(${formatHex(roundTripBytes)})) matches original`, );}
// ============================================================================// Step 7: bytesBase64Codec - Base64 Encoding for Bytes// ============================================================================printStep(7, 'bytesBase64Codec - Base64 Encoding for Bytes');
printInfo('bytesBase64Codec always encodes to base64 (both JSON and msgpack).');printInfo('Used for fields that are always base64 in wire format.');printInfo('');
const testBase64Bytes = [ new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]), // "Hello" in ASCII new Uint8Array([0xff, 0xfe, 0xfd]),];
for (const bytes of testBase64Bytes) { const encodedJson = bytesBase64Codec.encode(bytes, 'json'); const encodedMsgpack = bytesBase64Codec.encode(bytes, 'msgpack'); const decodedJson = bytesBase64Codec.decode(encodedJson, 'json'); const decodedMsgpack = bytesBase64Codec.decode(encodedMsgpack, 'msgpack');
printInfo(` ${formatHex(bytes)}:`); printInfo(` JSON: encode="${encodedJson}" (base64)`); printInfo( ` decode=${formatHex(decodedJson)}, matches=${arrayEqual(bytes, decodedJson)}`, ); printInfo(` msgpack: encode="${encodedMsgpack}" (base64)`); printInfo( ` decode=${formatHex(decodedMsgpack)}, matches=${arrayEqual(bytes, decodedMsgpack)}`, );}printInfo('');
// Round-trip verificationconst rtB64Bytes = new Uint8Array([0x01, 0x02, 0x03]);const rtB64Encoded = bytesBase64Codec.encode(rtB64Bytes, 'json');const rtB64Decoded = bytesBase64Codec.decode(rtB64Encoded, 'json');if (arrayEqual(rtB64Decoded, rtB64Bytes)) { printSuccess( `Round-trip: decode(encode(${formatHex(rtB64Bytes)})) via base64 "${rtB64Encoded}" matches original`, );}
// ============================================================================// Step 8: addressCodec - Encoding/Decoding Algorand Addresses// ============================================================================printStep(8, 'addressCodec - Encoding/Decoding Algorand Addresses');
printInfo('addressCodec handles Algorand Address objects.');printInfo('JSON format: encodes to 58-character base32 string');printInfo('msgpack format: encodes to 32-byte public key');printInfo('');
// Create test addressesconst zeroAddr = Address.zeroAddress();const testAddrBytes = new Uint8Array(32);for (let i = 0; i < 32; i++) testAddrBytes[i] = i;const testAddr = new Address(testAddrBytes);
const testAddresses = [zeroAddr, testAddr];
for (const addr of testAddresses) { const encodedJson = addressCodec.encode(addr, 'json'); const encodedMsgpack = addressCodec.encode(addr, 'msgpack'); const decodedJson = addressCodec.decode(encodedJson, 'json'); const decodedMsgpack = addressCodec.decode(encodedMsgpack, 'msgpack');
const displayAddr = `${addr.toString().slice(0, 12)}...`; printInfo(` ${displayAddr}:`); printInfo(` JSON: encode="${(encodedJson as string).slice(0, 12)}..." (base32 string)`); printInfo( ` decode=${decodedJson.toString().slice(0, 12)}..., matches=${addr.equals(decodedJson)}`, ); printInfo( ` msgpack: encode=${formatHex((encodedMsgpack as Uint8Array).slice(0, 8))}... (32-byte pubkey)`, ); printInfo( ` decode=${decodedMsgpack.toString().slice(0, 12)}..., matches=${addr.equals(decodedMsgpack)}`, );}printInfo('');
// Round-trip verificationconst roundTripAddr = testAddr;const rtAddrEncodedJson = addressCodec.encode(roundTripAddr, 'json');const rtAddrDecodedJson = addressCodec.decode(rtAddrEncodedJson, 'json');if (rtAddrDecodedJson.equals(roundTripAddr)) { printSuccess( `Round-trip (JSON): decode(encode(${roundTripAddr.toString().slice(0, 12)}...)) matches original`, );}
const rtAddrEncodedMsgpack = addressCodec.encode(roundTripAddr, 'msgpack');const rtAddrDecodedMsgpack = addressCodec.decode(rtAddrEncodedMsgpack, 'msgpack');if (rtAddrDecodedMsgpack.equals(roundTripAddr)) { printSuccess( `Round-trip (msgpack): decode(encode(${roundTripAddr.toString().slice(0, 12)}...)) matches original`, );}
// ============================================================================// Step 9: Format Differences Summary// ============================================================================printStep(9, 'Format Differences Summary');
printInfo('Codec behavior differs by format (json vs msgpack):');printInfo('');printInfo(' Codec | JSON Encoding | msgpack Encoding');printInfo(' -------------------|----------------------|-------------------');printInfo(' numberCodec | number | number');printInfo(' bigIntCodec | bigint | bigint');printInfo(' booleanCodec | boolean | boolean');printInfo(' stringCodec | string | string (or Uint8Array)');printInfo(' bytesCodec | base64 string | Uint8Array');printInfo(' bytesBase64Codec | base64 string | base64 string');printInfo(' addressCodec | 58-char base32 string| 32-byte Uint8Array');printInfo('');printSuccess('Codecs handle format-specific transformations automatically');
// ============================================================================// Step 10: Round-Trip Verification Summary// ============================================================================printStep(10, 'Round-Trip Verification Summary');
printInfo('All primitive codecs support perfect round-trip encoding:');printInfo('');
// Aggregate all round-trip resultsconst roundTrips: Array<{ name: string; value: string; format: EncodingFormat; success: boolean }> = [];
// Numberconst numVal = 42;roundTrips.push({ name: 'numberCodec', value: String(numVal), format: 'json', success: numberCodec.decode(numberCodec.encode(numVal, 'json'), 'json') === numVal,});
// BigIntconst bigVal = 9007199254740993n;roundTrips.push({ name: 'bigIntCodec', value: `${bigVal}n`, format: 'msgpack', success: bigIntCodec.decode(bigIntCodec.encode(bigVal, 'msgpack'), 'msgpack') === bigVal,});
// Booleanconst boolVal = true;roundTrips.push({ name: 'booleanCodec', value: String(boolVal), format: 'json', success: booleanCodec.decode(booleanCodec.encode(boolVal, 'json'), 'json') === boolVal,});
// Stringconst strVal = 'Algorand';roundTrips.push({ name: 'stringCodec', value: `"${strVal}"`, format: 'json', success: stringCodec.decode(stringCodec.encode(strVal, 'json'), 'json') === strVal,});
// Bytesconst bytesVal = new Uint8Array([1, 2, 3, 4, 5]);roundTrips.push({ name: 'bytesCodec', value: formatHex(bytesVal), format: 'json', success: arrayEqual(bytesCodec.decode(bytesCodec.encode(bytesVal, 'json'), 'json'), bytesVal),});roundTrips.push({ name: 'bytesCodec', value: formatHex(bytesVal), format: 'msgpack', success: arrayEqual( bytesCodec.decode(bytesCodec.encode(bytesVal, 'msgpack'), 'msgpack'), bytesVal, ),});
// BytesBase64const b64Val = new Uint8Array([0xff, 0xfe, 0xfd]);roundTrips.push({ name: 'bytesBase64Codec', value: formatHex(b64Val), format: 'json', success: arrayEqual( bytesBase64Codec.decode(bytesBase64Codec.encode(b64Val, 'json'), 'json'), b64Val, ),});
// Addressconst addrVal = testAddr;roundTrips.push({ name: 'addressCodec', value: `${addrVal.toString().slice(0, 12)}...`, format: 'json', success: addressCodec.decode(addressCodec.encode(addrVal, 'json'), 'json').equals(addrVal),});roundTrips.push({ name: 'addressCodec', value: `${addrVal.toString().slice(0, 12)}...`, format: 'msgpack', success: addressCodec.decode(addressCodec.encode(addrVal, 'msgpack'), 'msgpack').equals(addrVal),});
for (const rt of roundTrips) { const status = rt.success ? 'PASS' : 'FAIL'; printInfo(` [${status}] ${rt.name} (${rt.format}): ${rt.value}`);}
const allPassed = roundTrips.every(rt => rt.success);if (allPassed) { printInfo(''); printSuccess('All round-trip verifications passed!');}
// ============================================================================// Summary// ============================================================================printStep(11, 'Summary');
printInfo('Primitive codecs for Algorand wire format encoding:');printInfo('');printInfo(' Codec Interface:');printInfo(' - encode(value, format) - Transform to wire format');printInfo(' - decode(value, format) - Transform from wire format');printInfo(' - defaultValue() - Get type default (0, false, "", etc.)');printInfo('');printInfo(' Available Primitive Codecs:');printInfo(' - numberCodec - JavaScript numbers');printInfo(' - bigIntCodec - BigInt for uint64 values');printInfo(' - booleanCodec - true/false values');printInfo(' - stringCodec - UTF-8 strings');printInfo(' - bytesCodec - Raw binary data (Uint8Array)');printInfo(' - bytesBase64Codec - Always base64 encoded bytes');printInfo(' - addressCodec - Algorand Address objects');printInfo('');printInfo(' Format Support:');printInfo(' - "json" - JSON wire format (strings for bytes/addresses)');printInfo(' - "msgpack" - MessagePack format (binary for bytes/addresses)');printInfo('');printSuccess('Primitive Codecs Example completed!');