Skip to content

ABI Struct and Tuple Conversion

← Back to ABI Encoding

This example demonstrates how to convert between struct values (named) and tuple values (positional):

  • getTupleValueFromStructValue(): Convert struct object to tuple array
  • getStructValueFromTupleValue(): Convert tuple array back to struct object
  • Simple struct { name: ‘Alice’, age: 30n } <-> tuple [‘Alice’, 30n]
  • Nested structs with complex types
  • Verify that struct and tuple encodings produce identical bytes Key concept: Structs and tuples have identical binary encoding in ARC-4. The conversion functions allow you to work with the same data in either format:
  • Struct format: object with named properties (more readable)
  • Tuple format: array with positional elements (matches ABI encoding)
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example abi/09-struct-tuple-conversion.ts

View source on GitHub

09-struct-tuple-conversion.ts
/**
* Example: ABI Struct and Tuple Conversion
*
* This example demonstrates how to convert between struct values (named) and tuple values (positional):
* - getTupleValueFromStructValue(): Convert struct object to tuple array
* - getStructValueFromTupleValue(): Convert tuple array back to struct object
* - Simple struct { name: 'Alice', age: 30n } <-> tuple ['Alice', 30n]
* - Nested structs with complex types
* - Verify that struct and tuple encodings produce identical bytes
*
* Key concept: Structs and tuples have identical binary encoding in ARC-4.
* The conversion functions allow you to work with the same data in either format:
* - Struct format: object with named properties (more readable)
* - Tuple format: array with positional elements (matches ABI encoding)
*
* Prerequisites:
* - No LocalNet required
*/
import {
ABIStructType,
ABITupleType,
ABIType,
getStructValueFromTupleValue,
getTupleValueFromStructValue,
} from '@algorandfoundation/algokit-utils/abi'
import { formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
function main() {
printHeader('ABI Struct and Tuple Conversion Example')
// Step 1: Simple struct to tuple conversion
printStep(1, 'Simple Struct to Tuple Conversion')
// Create a struct type: { name: string, age: uint64 }
const personStruct = ABIStructType.fromStruct('Person', {
Person: [
{ name: 'name', type: 'string' },
{ name: 'age', type: 'uint64' },
],
})
printInfo(`Struct type: ${personStruct.structName}`)
printInfo(`ABI representation: ${personStruct.name}`)
// Define a struct value
const structValue = { name: 'Alice', age: 30n }
printInfo(`\nStruct value: { name: "${structValue.name}", age: ${structValue.age}n }`)
// Convert struct to tuple using getTupleValueFromStructValue()
const tupleValue = getTupleValueFromStructValue(personStruct, structValue)
printInfo('\nConverted to tuple using getTupleValueFromStructValue():')
printInfo(` Result: [${tupleValue.map((v) => (typeof v === 'string' ? `"${v}"` : `${v}n`)).join(', ')}]`)
printInfo(` tupleValue[0]: "${tupleValue[0]}" (name)`)
printInfo(` tupleValue[1]: ${tupleValue[1]}n (age)`)
// Step 2: Tuple to struct conversion
printStep(2, 'Tuple to Struct Conversion')
// Start with a tuple value
const inputTuple = ['Bob', 25n]
printInfo(`Tuple value: ["${inputTuple[0]}", ${inputTuple[1]}n]`)
// Convert tuple to struct using getStructValueFromTupleValue()
const convertedStruct = getStructValueFromTupleValue(personStruct, inputTuple)
printInfo('\nConverted to struct using getStructValueFromTupleValue():')
printInfo(` Result: { name: "${convertedStruct.name}", age: ${convertedStruct.age}n }`)
printInfo(` convertedStruct.name: "${convertedStruct.name}"`)
printInfo(` convertedStruct.age: ${convertedStruct.age}n`)
// Step 3: Round-trip conversion
printStep(3, 'Round-trip Conversion')
const original = { name: 'Charlie', age: 42n }
printInfo(`Original struct: { name: "${original.name}", age: ${original.age}n }`)
// Struct -> Tuple -> Struct
const asTuple = getTupleValueFromStructValue(personStruct, original)
printInfo(`After struct -> tuple: ["${asTuple[0]}", ${asTuple[1]}n]`)
const backToStruct = getStructValueFromTupleValue(personStruct, asTuple)
printInfo(`After tuple -> struct: { name: "${backToStruct.name}", age: ${backToStruct.age}n }`)
const roundTripMatch = original.name === backToStruct.name && original.age === backToStruct.age
printInfo(`\nRound-trip preserved values: ${roundTripMatch}`)
// Step 4: Verify identical encoding
printStep(4, 'Verify Identical Encoding')
printInfo('Both struct and tuple values should encode to identical bytes:')
// Encode using struct type
const structEncoded = personStruct.encode(structValue)
printInfo(`\nStruct encoded: ${formatHex(structEncoded)}`)
// Create equivalent tuple type and encode the tuple value
const tupleType = ABIType.from('(string,uint64)') as ABITupleType
const tupleEncoded = tupleType.encode(tupleValue as [string, bigint])
printInfo(`Tuple encoded: ${formatHex(tupleEncoded)}`)
// Compare encodings
const encodingsMatch =
structEncoded.length === tupleEncoded.length && structEncoded.every((byte, i) => byte === tupleEncoded[i])
printInfo(`\nEncodings are identical: ${encodingsMatch}`)
printInfo(`Total bytes: ${structEncoded.length}`)
// Step 5: Complex struct with more fields
printStep(5, 'Complex Struct with More Fields')
// Create a more complex struct
const userStruct = ABIStructType.fromStruct('User', {
User: [
{ name: 'id', type: 'uint64' },
{ name: 'username', type: 'string' },
{ name: 'active', type: 'bool' },
{ name: 'balance', type: 'uint256' },
],
})
const userValue = {
id: 12345n,
username: 'alice_wonder',
active: true,
balance: 1000000000000000000n, // 1 ETH in wei
}
printInfo(`Struct type: ${userStruct.structName}`)
printInfo(`Fields: id (uint64), username (string), active (bool), balance (uint256)`)
printInfo(`\nStruct value:`)
printInfo(` id: ${userValue.id}n`)
printInfo(` username: "${userValue.username}"`)
printInfo(` active: ${userValue.active}`)
printInfo(` balance: ${userValue.balance}n`)
// Convert to tuple
const userTuple = getTupleValueFromStructValue(userStruct, userValue)
printInfo('\nConverted to tuple:')
printInfo(` [${userTuple[0]}n, "${userTuple[1]}", ${userTuple[2]}, ${userTuple[3]}n]`)
// Convert back
const userBack = getStructValueFromTupleValue(userStruct, userTuple) as typeof userValue
printInfo('\nConverted back to struct:')
printInfo(` id: ${userBack.id}n`)
printInfo(` username: "${userBack.username}"`)
printInfo(` active: ${userBack.active}`)
printInfo(` balance: ${userBack.balance}n`)
// Verify encoding
const userStructEncoded = userStruct.encode(userValue)
const userTupleType = ABIType.from('(uint64,string,bool,uint256)') as ABITupleType
const userTupleEncoded = userTupleType.encode(userTuple as [bigint, string, boolean, bigint])
const userEncodingsMatch =
userStructEncoded.length === userTupleEncoded.length &&
userStructEncoded.every((byte, i) => byte === userTupleEncoded[i])
printInfo(`\nStruct and tuple encodings identical: ${userEncodingsMatch}`)
// Step 6: Nested struct conversion
printStep(6, 'Nested Struct Conversion')
// Create nested struct types
const orderStruct = ABIStructType.fromStruct('Order', {
Order: [
{ name: 'orderId', type: 'uint64' },
{ name: 'item', type: 'Item' },
{ name: 'quantity', type: 'uint32' },
],
Item: [
{ name: 'name', type: 'string' },
{ name: 'price', type: 'uint64' },
],
})
const orderValue = {
orderId: 1001n,
item: {
name: 'Widget',
price: 2500n,
},
quantity: 5,
}
printInfo('Nested struct type: Order containing Item')
printInfo(` Order: { orderId: uint64, item: Item, quantity: uint32 }`)
printInfo(` Item: { name: string, price: uint64 }`)
printInfo(`\nNested struct value:`)
printInfo(` orderId: ${orderValue.orderId}n`)
printInfo(` item: { name: "${orderValue.item.name}", price: ${orderValue.item.price}n }`)
printInfo(` quantity: ${orderValue.quantity}`)
// Convert nested struct to tuple
const orderTuple = getTupleValueFromStructValue(orderStruct, orderValue)
printInfo('\nConverted to nested tuple using getTupleValueFromStructValue():')
printInfo(` Result structure: [orderId, [name, price], quantity]`)
printInfo(` orderTuple[0]: ${orderTuple[0]}n (orderId)`)
printInfo(` orderTuple[1]: ["${(orderTuple[1] as string[])[0]}", ${(orderTuple[1] as bigint[])[1]}n] (item)`)
printInfo(` orderTuple[2]: ${orderTuple[2]} (quantity)`)
// Convert back to struct
const orderBack = getStructValueFromTupleValue(orderStruct, orderTuple) as typeof orderValue
printInfo('\nConverted back to struct using getStructValueFromTupleValue():')
printInfo(` orderId: ${orderBack.orderId}n`)
printInfo(` item.name: "${orderBack.item.name}"`)
printInfo(` item.price: ${orderBack.item.price}n`)
printInfo(` quantity: ${orderBack.quantity}`)
// Verify nested encoding
const orderStructEncoded = orderStruct.encode(orderValue)
const orderTupleType = ABIType.from('(uint64,(string,uint64),uint32)') as ABITupleType
const orderTupleEncoded = orderTupleType.encode(orderTuple as [bigint, [string, bigint], number])
const orderEncodingsMatch =
orderStructEncoded.length === orderTupleEncoded.length &&
orderStructEncoded.every((byte, i) => byte === orderTupleEncoded[i])
printInfo(`\nNested struct and tuple encodings identical: ${orderEncodingsMatch}`)
printInfo(`Total bytes: ${orderStructEncoded.length}`)
// Step 7: Deeply nested struct
printStep(7, 'Deeply Nested Struct')
// Create deeply nested struct
const companyStruct = ABIStructType.fromStruct('Company', {
Company: [
{ name: 'name', type: 'string' },
{ name: 'ceo', type: 'Employee' },
],
Employee: [
{ name: 'name', type: 'string' },
{ name: 'contact', type: 'Contact' },
],
Contact: [
{ name: 'email', type: 'string' },
{ name: 'phone', type: 'string' },
],
})
const companyValue = {
name: 'TechCorp',
ceo: {
name: 'Jane Doe',
contact: {
phone: '+1-555-0100',
},
},
}
printInfo('Deeply nested struct: Company -> Employee -> Contact')
printInfo(`\nCompany value:`)
printInfo(` name: "${companyValue.name}"`)
printInfo(` ceo.name: "${companyValue.ceo.name}"`)
printInfo(` ceo.contact.email: "${companyValue.ceo.contact.email}"`)
printInfo(` ceo.contact.phone: "${companyValue.ceo.contact.phone}"`)
// Convert to deeply nested tuple
const companyTuple = getTupleValueFromStructValue(companyStruct, companyValue)
printInfo('\nConverted to deeply nested tuple:')
printInfo(` Structure: [name, [employeeName, [email, phone]]]`)
const ceoTuple = companyTuple[1] as unknown[]
const contactTuple = ceoTuple[1] as string[]
printInfo(` companyTuple[0]: "${companyTuple[0]}"`)
printInfo(` companyTuple[1][0]: "${ceoTuple[0]}"`)
printInfo(` companyTuple[1][1][0]: "${contactTuple[0]}"`)
printInfo(` companyTuple[1][1][1]: "${contactTuple[1]}"`)
// Convert back
const companyBack = getStructValueFromTupleValue(companyStruct, companyTuple) as typeof companyValue
printInfo('\nConverted back to struct:')
printInfo(` name: "${companyBack.name}"`)
printInfo(` ceo.name: "${companyBack.ceo.name}"`)
printInfo(` ceo.contact.email: "${companyBack.ceo.contact.email}"`)
printInfo(` ceo.contact.phone: "${companyBack.ceo.contact.phone}"`)
// Verify deep nesting encoding
const companyStructEncoded = companyStruct.encode(companyValue)
const companyTupleType = ABIType.from('(string,(string,(string,string)))') as ABITupleType
const companyTupleEncoded = companyTupleType.encode(companyTuple as [string, [string, [string, string]]])
const companyEncodingsMatch =
companyStructEncoded.length === companyTupleEncoded.length &&
companyStructEncoded.every((byte, i) => byte === companyTupleEncoded[i])
printInfo(`\nDeeply nested struct and tuple encodings identical: ${companyEncodingsMatch}`)
// Step 8: Use cases for conversion functions
printStep(8, 'Use Cases for Conversion Functions')
printInfo('When to use getTupleValueFromStructValue():')
printInfo(' - Converting struct data to pass to tuple-expecting ABI methods')
printInfo(' - Serializing struct data in a position-based format')
printInfo(' - Working with libraries that expect array/tuple format')
printInfo(' - Building raw transaction arguments')
printInfo('\nWhen to use getStructValueFromTupleValue():')
printInfo(' - Converting decoded tuple results to readable struct format')
printInfo(' - Adding field names to positional data for debugging')
printInfo(' - Working with APIs that return tuple arrays')
printInfo(' - Making code more maintainable with named fields')
// Step 9: Summary
printStep(9, 'Summary')
printInfo('Conversion functions:')
printInfo(' - getTupleValueFromStructValue(structType, structValue) -> ABIValue[]')
printInfo(' - getStructValueFromTupleValue(structType, tupleValue) -> ABIStructValue')
printInfo('\nKey points:')
printInfo(' - Structs and tuples are interchangeable at the binary level')
printInfo(' - Conversion is lossless - round-trip preserves all values')
printInfo(' - Nested structs convert to nested tuples and vice versa')
printInfo(' - Field names provide semantic meaning without affecting encoding')
printInfo(' - Use struct format for readability, tuple format for ABI compatibility')
printInfo('\nBinary equivalence verified:')
printInfo(' - Simple struct { name, age } = tuple (string, uint64)')
printInfo(' - Complex struct { id, username, active, balance } = tuple (uint64, string, bool, uint256)')
printInfo(' - Nested struct Order { Item { ... } } = nested tuple (...)')
printSuccess('ABI Struct and Tuple Conversion example completed successfully!')
}
main()