Skip to content

Composite Codecs

← Back to Common Utilities

This example demonstrates how to use composite codec types for encoding/decoding arrays, maps, and records in wire format (JSON or MessagePack). Topics covered:

  • ArrayCodec for encoding/decoding typed arrays
  • Pre-built array codecs: numberArrayCodec, stringArrayCodec, bigIntArrayCodec, booleanArrayCodec, bytesArrayCodec, addressArrayCodec
  • MapCodec for encoding/decoding Map<K, V> types
  • RecordCodec for encoding/decoding Record<string, V> types
  • Creating custom ArrayCodec with a specific element codec
  • Encoding nested structures (array of arrays, map of arrays)
  • Round-trip verification for each composite codec
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example common/10-composite-codecs.ts

View source on GitHub

10-composite-codecs.ts
/**
* Example: Composite Codecs
*
* This example demonstrates how to use composite codec types for encoding/decoding
* arrays, maps, and records in wire format (JSON or MessagePack).
*
* Topics covered:
* - ArrayCodec for encoding/decoding typed arrays
* - Pre-built array codecs: numberArrayCodec, stringArrayCodec, bigIntArrayCodec,
* booleanArrayCodec, bytesArrayCodec, addressArrayCodec
* - MapCodec for encoding/decoding Map<K, V> types
* - RecordCodec for encoding/decoding Record<string, V> types
* - Creating custom ArrayCodec with a specific element codec
* - Encoding nested structures (array of arrays, map of arrays)
* - Round-trip verification for each composite codec
*
* Prerequisites:
* - No LocalNet required
*/
import type { EncodingFormat } from '@algorandfoundation/algokit-utils/common'
import {
// Utilities
Address,
// Composite codecs
ArrayCodec,
MapCodec,
RecordCodec,
addressArrayCodec,
addressCodec,
arrayEqual,
bigIntArrayCodec,
bigIntCodec,
booleanArrayCodec,
bytesArrayCodec,
bytesCodec,
// Pre-built array codecs
numberArrayCodec,
// Primitive codecs (for creating custom composites)
numberCodec,
stringArrayCodec,
stringCodec,
} from '@algorandfoundation/algokit-utils/common'
import { formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
// ============================================================================
// Main Example
// ============================================================================
printHeader('Composite Codecs Example')
// ============================================================================
// Step 1: Introduction to Composite Codecs
// ============================================================================
printStep(1, 'Introduction to Composite Codecs')
printInfo('Composite codecs build on primitive codecs to handle complex data structures.')
printInfo('')
printInfo('Three composite codec types:')
printInfo(' ArrayCodec<T> - Encodes/decodes arrays of type T[]')
printInfo(' MapCodec<K, V> - Encodes/decodes Map<K, V>')
printInfo(' RecordCodec<V> - Encodes/decodes Record<string, V>')
printInfo('')
printInfo('Each uses an underlying codec for element encoding/decoding.')
printInfo('')
printSuccess('Composite codecs extend primitive codecs for collections')
// ============================================================================
// Step 2: ArrayCodec - Typed Array Encoding/Decoding
// ============================================================================
printStep(2, 'ArrayCodec - Typed Array Encoding/Decoding')
printInfo('ArrayCodec wraps an element codec to encode/decode arrays.')
printInfo('Creating an ArrayCodec: new ArrayCodec(elementCodec)')
printInfo('')
// Create a custom ArrayCodec with numberCodec
const customNumberArray = new ArrayCodec(numberCodec)
const numbers = [1, 2, 3, 4, 5]
printInfo(`Original array: [${numbers.join(', ')}]`)
const encodedJson = customNumberArray.encode(numbers, 'json')
const encodedMsgpack = customNumberArray.encode(numbers, 'msgpack')
const decodedJson = customNumberArray.decode(encodedJson, 'json')
const decodedMsgpack = customNumberArray.decode(encodedMsgpack, 'msgpack')
printInfo(`JSON: encoded=[${encodedJson.join(', ')}], decoded=[${decodedJson.join(', ')}]`)
printInfo(`msgpack: encoded=[${encodedMsgpack.join(', ')}], decoded=[${decodedMsgpack.join(', ')}]`)
printInfo('')
// Default value
printInfo(`ArrayCodec default value: [${customNumberArray.defaultValue().join(', ')}] (empty array)`)
printInfo('')
printSuccess('ArrayCodec encodes each element using the underlying codec')
// ============================================================================
// Step 3: Pre-built Array Codecs
// ============================================================================
printStep(3, 'Pre-built Array Codecs')
printInfo('algokit-utils provides pre-built array codecs for common types:')
printInfo('')
// numberArrayCodec
printInfo('numberArrayCodec - for number arrays:')
const numberArray = [10, 20, 30, 40]
const numEncoded = numberArrayCodec.encode(numberArray, 'json')
const numDecoded = numberArrayCodec.decode(numEncoded, 'json')
printInfo(` Original: [${numberArray.join(', ')}]`)
printInfo(` Decoded: [${numDecoded.join(', ')}]`)
printInfo(` Match: ${numberArray.every((n, i) => n === numDecoded[i])}`)
printInfo('')
// stringArrayCodec
printInfo('stringArrayCodec - for string arrays:')
const stringArray = ['Alice', 'Bob', 'Charlie']
const strEncoded = stringArrayCodec.encode(stringArray, 'json')
const strDecoded = stringArrayCodec.decode(strEncoded, 'json')
printInfo(` Original: ["${stringArray.join('", "')}"]`)
printInfo(` Decoded: ["${strDecoded.join('", "')}"]`)
printInfo(` Match: ${stringArray.every((s, i) => s === strDecoded[i])}`)
printInfo('')
// bigIntArrayCodec
printInfo('bigIntArrayCodec - for BigInt arrays:')
const bigIntArray = [100n, 9007199254740993n, 18446744073709551615n]
const bigEncoded = bigIntArrayCodec.encode(bigIntArray, 'msgpack')
const bigDecoded = bigIntArrayCodec.decode(bigEncoded, 'msgpack')
printInfo(` Original: [${bigIntArray.map((b) => `${b}n`).join(', ')}]`)
printInfo(` Decoded: [${bigDecoded.map((b: bigint) => `${b}n`).join(', ')}]`)
printInfo(` Match: ${bigIntArray.every((b, i) => b === bigDecoded[i])}`)
printInfo('')
// booleanArrayCodec
printInfo('booleanArrayCodec - for boolean arrays:')
const boolArray = [true, false, true, false]
const boolEncoded = booleanArrayCodec.encode(boolArray, 'json')
const boolDecoded = booleanArrayCodec.decode(boolEncoded, 'json')
printInfo(` Original: [${boolArray.join(', ')}]`)
printInfo(` Decoded: [${boolDecoded.join(', ')}]`)
printInfo(` Match: ${boolArray.every((b, i) => b === boolDecoded[i])}`)
printInfo('')
// bytesArrayCodec
printInfo('bytesArrayCodec - for Uint8Array arrays:')
const bytesArray = [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6]), new Uint8Array([7, 8, 9])]
const bytesEncoded = bytesArrayCodec.encode(bytesArray, 'json')
const bytesDecoded = bytesArrayCodec.decode(bytesEncoded, 'json')
printInfo(` Original: [${bytesArray.map(formatHex).join(', ')}]`)
printInfo(` Decoded: [${bytesDecoded.map(formatHex).join(', ')}]`)
printInfo(` Match: ${bytesArray.every((b, i) => arrayEqual(b, bytesDecoded[i]))}`)
printInfo('')
// addressArrayCodec
printInfo('addressArrayCodec - for Address arrays:')
const addrBytes1 = new Uint8Array(32).fill(1)
const addrBytes2 = new Uint8Array(32).fill(2)
const addressArray = [new Address(addrBytes1), new Address(addrBytes2)]
const addrEncoded = addressArrayCodec.encode(addressArray, 'json')
const addrDecoded = addressArrayCodec.decode(addrEncoded, 'json')
printInfo(` Original: [${addressArray.map((a) => `${a.toString().slice(0, 12) }...`).join(', ')}]`)
printInfo(` Decoded: [${addrDecoded.map((a: Address) => `${a.toString().slice(0, 12) }...`).join(', ')}]`)
printInfo(` Match: ${addressArray.every((a, i) => a.equals(addrDecoded[i]))}`)
printInfo('')
printSuccess('Pre-built array codecs available for all primitive types')
// ============================================================================
// Step 4: MapCodec - Map<K, V> Encoding/Decoding
// ============================================================================
printStep(4, 'MapCodec - Map<K, V> Encoding/Decoding')
printInfo('MapCodec handles Maps with various key types.')
printInfo('Creating a MapCodec: new MapCodec(keyCodec, valueCodec)')
printInfo('')
// Map with string keys and number values
printInfo('Map<string, number> example:')
const stringNumberMap = new MapCodec(stringCodec, numberCodec)
const strNumMap = new Map<string, number>([
['alice', 100],
['bob', 200],
['charlie', 300],
])
printInfo(` Original: Map { ${Array.from(strNumMap.entries()).map(([k, v]) => `"${k}" => ${v}`).join(', ')} }`)
const mapEncodedJson = stringNumberMap.encode(strNumMap, 'json')
const mapDecodedJson = stringNumberMap.decode(mapEncodedJson, 'json')
printInfo(` JSON encoded: ${JSON.stringify(mapEncodedJson)}`)
const mapDecodedJsonEntries = [...mapDecodedJson.entries()]
printInfo(` JSON decoded: Map { ${mapDecodedJsonEntries.map(([k, v]) => `"${k}" => ${v}`).join(', ')} }`)
const mapEncodedMsgpack = stringNumberMap.encode(strNumMap, 'msgpack')
const mapDecodedMsgpack = stringNumberMap.decode(mapEncodedMsgpack, 'msgpack')
printInfo(` msgpack encoded: Map (preserved as Map)`)
const mapDecodedMsgpackEntries = [...mapDecodedMsgpack.entries()]
printInfo(` msgpack decoded: Map { ${mapDecodedMsgpackEntries.map(([k, v]) => `"${k}" => ${v}`).join(', ')} }`)
printInfo('')
// Map with bigint keys
printInfo('Map<bigint, string> example (bigint keys become strings in JSON):')
const bigintStringMap = new MapCodec(bigIntCodec, stringCodec)
const bigStrMap = new Map<bigint, string>([
[1n, 'one'],
[2n, 'two'],
[9007199254740993n, 'large'],
])
printInfo(` Original: Map { ${Array.from(bigStrMap.entries()).map(([k, v]) => `${k}n => "${v}"`).join(', ')} }`)
const bigMapEncodedJson = bigintStringMap.encode(bigStrMap, 'json')
const bigMapDecodedJson = bigintStringMap.decode(bigMapEncodedJson, 'json')
printInfo(` JSON encoded: ${JSON.stringify(bigMapEncodedJson)} (keys as strings)`)
const bigMapDecodedEntries = [...bigMapDecodedJson.entries()]
printInfo(` JSON decoded: Map { ${bigMapDecodedEntries.map(([k, v]) => `${k}n => "${v}"`).join(', ')} }`)
printInfo('')
// Default value
printInfo(`MapCodec default value: Map(${stringNumberMap.defaultValue().size}) (empty map)`)
printInfo('')
printSuccess('MapCodec supports string and bigint keys in JSON, all types in msgpack')
// ============================================================================
// Step 5: RecordCodec - Record<string, V> Encoding/Decoding
// ============================================================================
printStep(5, 'RecordCodec - Record<string, V> Encoding/Decoding')
printInfo('RecordCodec handles string-keyed objects with homogeneous values.')
printInfo('Creating a RecordCodec: new RecordCodec(valueCodec)')
printInfo('')
// Record with number values
printInfo('Record<string, number> example:')
const numberRecord = new RecordCodec(numberCodec)
const scores: Record<string, number> = {
alice: 95,
bob: 87,
charlie: 92,
}
printInfo(` Original: { ${Object.entries(scores).map(([k, v]) => `${k}: ${v}`).join(', ')} }`)
const recEncodedJson = numberRecord.encode(scores, 'json')
const recDecodedJson = numberRecord.decode(recEncodedJson, 'json')
printInfo(` JSON encoded: ${JSON.stringify(recEncodedJson)}`)
printInfo(` JSON decoded: { ${Object.entries(recDecodedJson).map(([k, v]) => `${k}: ${v}`).join(', ')} }`)
const recEncodedMsgpack = numberRecord.encode(scores, 'msgpack')
const recDecodedMsgpack = numberRecord.decode(recEncodedMsgpack, 'msgpack')
printInfo(` msgpack encoded/decoded: { ${Object.entries(recDecodedMsgpack).map(([k, v]) => `${k}: ${v}`).join(', ')} }`)
printInfo('')
// Record with string values
printInfo('Record<string, string> example:')
const stringRecord = new RecordCodec(stringCodec)
const metadata: Record<string, string> = {
name: 'My App',
version: '1.0.0',
author: 'Algorand Developer',
}
printInfo(` Original: { ${Object.entries(metadata).map(([k, v]) => `${k}: "${v}"`).join(', ')} }`)
const metaEncoded = stringRecord.encode(metadata, 'json')
const metaDecoded = stringRecord.decode(metaEncoded, 'json')
printInfo(` Decoded: { ${Object.entries(metaDecoded).map(([k, v]) => `${k}: "${v}"`).join(', ')} }`)
printInfo('')
// Default value
printInfo(`RecordCodec default value: {} (empty object)`)
printInfo(` Object.keys(defaultValue()).length = ${Object.keys(numberRecord.defaultValue()).length}`)
printInfo('')
printSuccess('RecordCodec simplifies string-keyed object encoding')
// ============================================================================
// Step 6: Custom ArrayCodec with Specific Element Codec
// ============================================================================
printStep(6, 'Custom ArrayCodec with Specific Element Codec')
printInfo('You can create custom ArrayCodecs for any element type.')
printInfo('')
// Custom codec for arrays of addresses (same as addressArrayCodec)
printInfo('Creating custom ArrayCodec<Address>:')
const customAddressArray = new ArrayCodec(addressCodec)
const testAddresses = [Address.zeroAddress(), new Address(new Uint8Array(32).fill(0xab))]
const customAddrEncoded = customAddressArray.encode(testAddresses, 'json')
const customAddrDecoded = customAddressArray.decode(customAddrEncoded, 'json')
printInfo(` Original: [${testAddresses.map((a) => `${a.toString().slice(0, 12) }...`).join(', ')}]`)
printInfo(` Encoded (JSON): [${(customAddrEncoded as string[]).map((s) => `${s.slice(0, 12) }...`).join(', ')}]`)
printInfo(` Decoded: [${customAddrDecoded.map((a: Address) => `${a.toString().slice(0, 12) }...`).join(', ')}]`)
printInfo('')
// Custom codec for arrays of bytes (same as bytesArrayCodec)
printInfo('Creating custom ArrayCodec<Uint8Array>:')
const customBytesArray = new ArrayCodec(bytesCodec)
const testBytesArrays = [new Uint8Array([0xde, 0xad]), new Uint8Array([0xbe, 0xef])]
const customBytesEncoded = customBytesArray.encode(testBytesArrays, 'json')
const customBytesDecoded = customBytesArray.decode(customBytesEncoded, 'json')
printInfo(` Original: [${testBytesArrays.map(formatHex).join(', ')}]`)
printInfo(` Encoded (JSON): ["${(customBytesEncoded as string[]).join('", "')}"] (base64)`)
printInfo(` Decoded: [${customBytesDecoded.map(formatHex).join(', ')}]`)
printInfo('')
printSuccess('Custom ArrayCodecs can wrap any primitive or composite codec')
// ============================================================================
// Step 7: Nested Structures - Array of Arrays
// ============================================================================
printStep(7, 'Nested Structures - Array of Arrays')
printInfo('Composite codecs can be nested for complex structures.')
printInfo('')
// Array of number arrays (2D array)
printInfo('Array of number arrays (2D matrix):')
const numberArrayCodecInstance = new ArrayCodec(numberCodec)
const arrayOfNumberArrays = new ArrayCodec(numberArrayCodecInstance)
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
printInfo(` Original matrix:`)
for (const row of matrix) {
printInfo(` [${row.join(', ')}]`)
}
const matrixEncoded = arrayOfNumberArrays.encode(matrix, 'json')
const matrixDecoded = arrayOfNumberArrays.decode(matrixEncoded, 'json')
printInfo(` Decoded matrix:`)
for (const row of matrixDecoded) {
printInfo(` [${row.join(', ')}]`)
}
// Verify match
const matrixMatch = matrix.every((row, i) => row.every((val, j) => val === matrixDecoded[i][j]))
printInfo(` Match: ${matrixMatch}`)
printInfo('')
// Array of string arrays
printInfo('Array of string arrays (groups of names):')
const stringArrayCodecInstance = new ArrayCodec(stringCodec)
const arrayOfStringArrays = new ArrayCodec(stringArrayCodecInstance)
const groups = [
['Alice', 'Bob'],
['Charlie', 'David', 'Eve'],
]
printInfo(` Original: [${groups.map((g) => `["${g.join('", "')}"]`).join(', ')}]`)
const groupsEncoded = arrayOfStringArrays.encode(groups, 'json')
const groupsDecoded = arrayOfStringArrays.decode(groupsEncoded, 'json')
printInfo(` Decoded: [${groupsDecoded.map((g: string[]) => `["${g.join('", "')}"]`).join(', ')}]`)
printInfo('')
printSuccess('Nested ArrayCodecs enable multi-dimensional arrays')
// ============================================================================
// Step 8: Nested Structures - Map of Arrays
// ============================================================================
printStep(8, 'Nested Structures - Map of Arrays')
printInfo('MapCodec can use array codecs as value codecs.')
printInfo('')
// Map<string, number[]> - users to scores
printInfo('Map<string, number[]> example (user scores):')
const mapOfNumberArrays = new MapCodec(stringCodec, numberArrayCodec)
const userScores = new Map<string, number[]>([
['alice', [95, 88, 92]],
['bob', [78, 85, 90]],
])
printInfo(` Original:`)
for (const [user, scoreList] of userScores) {
printInfo(` "${user}" => [${scoreList.join(', ')}]`)
}
const userScoresEncodedJson = mapOfNumberArrays.encode(userScores, 'json')
const userScoresDecodedJson = mapOfNumberArrays.decode(userScoresEncodedJson, 'json')
printInfo(` JSON encoded: ${JSON.stringify(userScoresEncodedJson)}`)
printInfo(` Decoded:`)
for (const [user, scoreList] of userScoresDecodedJson) {
printInfo(` "${user}" => [${scoreList.join(', ')}]`)
}
printInfo('')
// Map<string, string[]> - categories to items
printInfo('Map<string, string[]> example (category items):')
const mapOfStringArrays = new MapCodec(stringCodec, stringArrayCodec)
const categories = new Map<string, string[]>([
['fruits', ['apple', 'banana', 'cherry']],
['colors', ['red', 'green', 'blue']],
])
printInfo(` Original:`)
for (const [cat, items] of categories) {
printInfo(` "${cat}" => ["${items.join('", "')}"]`)
}
const categoriesEncoded = mapOfStringArrays.encode(categories, 'msgpack')
const categoriesDecoded = mapOfStringArrays.decode(categoriesEncoded, 'msgpack')
printInfo(` msgpack Decoded:`)
for (const [cat, items] of categoriesDecoded) {
printInfo(` "${cat}" => ["${items.join('", "')}"]`)
}
printInfo('')
printSuccess('Composite codecs can be combined for complex structures')
// ============================================================================
// Step 9: Round-Trip Verification for All Composite Codecs
// ============================================================================
printStep(9, 'Round-Trip Verification for All Composite Codecs')
printInfo('Verifying decode(encode(value)) === value for all composite codecs:')
printInfo('')
const roundTrips: Array<{ name: string; value: string; format: EncodingFormat; success: boolean }> = []
// numberArrayCodec
const rtNumArray = [1, 2, 3, 4, 5]
roundTrips.push({
name: 'numberArrayCodec',
value: `[${rtNumArray.join(', ')}]`,
format: 'json',
success: rtNumArray.every((n, i) => n === numberArrayCodec.decode(numberArrayCodec.encode(rtNumArray, 'json'), 'json')[i]),
})
// stringArrayCodec
const rtStrArray = ['a', 'b', 'c']
roundTrips.push({
name: 'stringArrayCodec',
value: `["${rtStrArray.join('", "')}"]`,
format: 'json',
success: rtStrArray.every((s, i) => s === stringArrayCodec.decode(stringArrayCodec.encode(rtStrArray, 'json'), 'json')[i]),
})
// bigIntArrayCodec
const rtBigArray = [100n, 200n, 300n]
roundTrips.push({
name: 'bigIntArrayCodec',
value: `[${rtBigArray.map((b) => `${b}n`).join(', ')}]`,
format: 'msgpack',
success: rtBigArray.every(
(b, i) => b === bigIntArrayCodec.decode(bigIntArrayCodec.encode(rtBigArray, 'msgpack'), 'msgpack')[i],
),
})
// booleanArrayCodec
const rtBoolArray = [true, false, true]
roundTrips.push({
name: 'booleanArrayCodec',
value: `[${rtBoolArray.join(', ')}]`,
format: 'json',
success: rtBoolArray.every(
(b, i) => b === booleanArrayCodec.decode(booleanArrayCodec.encode(rtBoolArray, 'json'), 'json')[i],
),
})
// bytesArrayCodec
const rtBytesArray = [new Uint8Array([1, 2]), new Uint8Array([3, 4])]
const rtBytesDecoded = bytesArrayCodec.decode(bytesArrayCodec.encode(rtBytesArray, 'json'), 'json')
roundTrips.push({
name: 'bytesArrayCodec',
value: `[${rtBytesArray.map(formatHex).join(', ')}]`,
format: 'json',
success: rtBytesArray.every((b, i) => arrayEqual(b, rtBytesDecoded[i])),
})
// addressArrayCodec
const rtAddrArray = [Address.zeroAddress(), new Address(new Uint8Array(32).fill(0xff))]
const rtAddrDecoded = addressArrayCodec.decode(addressArrayCodec.encode(rtAddrArray, 'json'), 'json')
roundTrips.push({
name: 'addressArrayCodec',
value: `[${rtAddrArray.map((a) => `${a.toString().slice(0, 8) }...`).join(', ')}]`,
format: 'json',
success: rtAddrArray.every((a, i) => a.equals(rtAddrDecoded[i])),
})
// MapCodec<string, number>
const rtStrNumMap = new Map<string, number>([
['a', 1],
['b', 2],
])
const strNumMapCodec = new MapCodec(stringCodec, numberCodec)
const rtStrNumDecoded = strNumMapCodec.decode(strNumMapCodec.encode(rtStrNumMap, 'json'), 'json')
roundTrips.push({
name: 'MapCodec<string, number>',
value: 'Map { "a" => 1, "b" => 2 }',
format: 'json',
success: Array.from(rtStrNumMap.entries()).every(([k, v]) => rtStrNumDecoded.get(k) === v),
})
// MapCodec<bigint, string>
const rtBigStrMap = new Map<bigint, string>([
[1n, 'one'],
[2n, 'two'],
])
const bigStrMapCodec = new MapCodec(bigIntCodec, stringCodec)
const rtBigStrDecoded = bigStrMapCodec.decode(bigStrMapCodec.encode(rtBigStrMap, 'json'), 'json')
roundTrips.push({
name: 'MapCodec<bigint, string>',
value: 'Map { 1n => "one", 2n => "two" }',
format: 'json',
success: Array.from(rtBigStrMap.entries()).every(([k, v]) => rtBigStrDecoded.get(k) === v),
})
// RecordCodec<number>
const rtNumRecord = { a: 1, b: 2, c: 3 }
const numRecordCodec = new RecordCodec(numberCodec)
const rtNumRecDecoded = numRecordCodec.decode(numRecordCodec.encode(rtNumRecord, 'json'), 'json')
roundTrips.push({
name: 'RecordCodec<number>',
value: '{ a: 1, b: 2, c: 3 }',
format: 'json',
success: Object.entries(rtNumRecord).every(([k, v]) => rtNumRecDecoded[k] === v),
})
// RecordCodec<string>
const rtStrRecord = { name: 'test', type: 'example' }
const strRecordCodec = new RecordCodec(stringCodec)
const rtStrRecDecoded = strRecordCodec.decode(strRecordCodec.encode(rtStrRecord, 'json'), 'json')
roundTrips.push({
name: 'RecordCodec<string>',
value: '{ name: "test", type: "example" }',
format: 'json',
success: Object.entries(rtStrRecord).every(([k, v]) => rtStrRecDecoded[k] === v),
})
// Nested: Array of arrays
const rtNestedArray = [
[1, 2],
[3, 4],
]
const nestedArrayCodec = new ArrayCodec(new ArrayCodec(numberCodec))
const rtNestedDecoded = nestedArrayCodec.decode(nestedArrayCodec.encode(rtNestedArray, 'json'), 'json')
roundTrips.push({
name: 'ArrayCodec (nested)',
value: '[[1, 2], [3, 4]]',
format: 'json',
success: rtNestedArray.every((row, i) => row.every((val, j) => val === rtNestedDecoded[i][j])),
})
// Nested: Map of arrays
const rtMapOfArrays = new Map<string, number[]>([
['x', [1, 2, 3]],
['y', [4, 5, 6]],
])
const mapOfArraysCodec = new MapCodec(stringCodec, numberArrayCodec)
const rtMapOfArraysDecoded = mapOfArraysCodec.decode(mapOfArraysCodec.encode(rtMapOfArrays, 'json'), 'json')
roundTrips.push({
name: 'MapCodec (with array values)',
value: 'Map { "x" => [1,2,3], "y" => [4,5,6] }',
format: 'json',
success: Array.from(rtMapOfArrays.entries()).every(([k, v]) => {
const decoded = rtMapOfArraysDecoded.get(k)
return decoded && v.every((val, i) => val === decoded[i])
}),
})
// Display results
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!')
}
// ============================================================================
// Step 10: Summary
// ============================================================================
printStep(10, 'Summary')
printInfo('Composite codecs for complex data structures:')
printInfo('')
printInfo(' ArrayCodec<T>:')
printInfo(' - new ArrayCodec(elementCodec)')
printInfo(' - Encodes/decodes T[] using the element codec')
printInfo(' - Default value: [] (empty array)')
printInfo('')
printInfo(' Pre-built Array Codecs:')
printInfo(' - numberArrayCodec - number[]')
printInfo(' - stringArrayCodec - string[]')
printInfo(' - bigIntArrayCodec - bigint[]')
printInfo(' - booleanArrayCodec - boolean[]')
printInfo(' - bytesArrayCodec - Uint8Array[]')
printInfo(' - addressArrayCodec - Address[]')
printInfo('')
printInfo(' MapCodec<K, V>:')
printInfo(' - new MapCodec(keyCodec, valueCodec)')
printInfo(' - JSON: string/bigint keys, encoded as object')
printInfo(' - msgpack: any key type, preserved as Map')
printInfo(' - Default value: new Map() (empty map)')
printInfo('')
printInfo(' RecordCodec<V>:')
printInfo(' - new RecordCodec(valueCodec)')
printInfo(' - Encodes/decodes Record<string, V>')
printInfo(' - Default value: {} (empty object)')
printInfo('')
printInfo(' Nesting:')
printInfo(' - Composite codecs can be nested')
printInfo(' - ArrayCodec(ArrayCodec(...)) for 2D arrays')
printInfo(' - MapCodec(keyCodec, ArrayCodec(...)) for map of arrays')
printInfo('')
printSuccess('Composite Codecs Example completed!')