suix_getDynamicFields
Returns the list of dynamic field objects owned by an object on the Sui network.
Overview​
The suix_getDynamicFields
method allows you to query dynamic fields attached to any Sui object. Dynamic fields provide a way to add heterogeneous data to objects after creation, enabling flexible data structures and extensible object designs. This method is essential for exploring complex object hierarchies and understanding object relationships in Sui applications.
Parameters​
Parameter | Type | Required | Description |
---|---|---|---|
parentId | string | Yes | The ID of the parent object to query for dynamic fields |
cursor | string | No | Optional paging cursor for large result sets |
limit | number | No | Maximum number of items to return (default: 50, max: 50) |
Object ID Format​
- Object IDs are 66-character hex strings with
0x
prefix - Must be a valid Sui object ID
- The parent object must exist and be accessible
Returns​
Returns a paginated list of dynamic field information.
Field | Type | Description |
---|---|---|
data | array | Array of dynamic field objects |
nextCursor | string | Cursor for the next page of results (if applicable) |
hasNextPage | boolean | Whether there are more results available |
Dynamic Field Object Structure​
Each dynamic field object contains:
Field | Type | Description |
---|---|---|
name | object | The field name (type and value) |
bcsName | string | BCS-encoded field name |
type | string | The type of the dynamic field |
objectType | string | The type of the field value |
objectId | string | The object ID of the field value |
version | string | Object version |
digest | string | Object digest |
Code Examples​
- cURL
- JavaScript
- Python
# Get dynamic fields for an object
curl -X POST https://sui-mainnet.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "suix_getDynamicFields",
"params": [
"0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f"
],
"id": 1
}'
# Get dynamic fields with pagination
curl -X POST https://sui-mainnet.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "suix_getDynamicFields",
"params": [
"0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
null,
10
],
"id": 1
}'
import { SuiClient } from '@mysten/sui.js/client';
const client = new SuiClient({
url: 'https://sui-mainnet.dwellir.com/YOUR_API_KEY'
});
// Get all dynamic fields for an object
async function getDynamicFields(parentId) {
try {
const dynamicFields = await client.getDynamicFields({
parentId: parentId
});
console.log(`Found ${dynamicFields.data.length} dynamic fields`);
console.log('Has next page:', dynamicFields.hasNextPage);
return dynamicFields;
} catch (error) {
console.error('Failed to get dynamic fields:', error);
return null;
}
}
// Get dynamic fields with pagination
async function getDynamicFieldsPaginated(parentId, limit = 10) {
let allFields = [];
let cursor = null;
let hasNextPage = true;
while (hasNextPage) {
try {
const result = await client.getDynamicFields({
parentId: parentId,
cursor: cursor,
limit: limit
});
allFields = allFields.concat(result.data);
cursor = result.nextCursor;
hasNextPage = result.hasNextPage;
console.log(`Fetched ${result.data.length} fields, total: ${allFields.length}`);
if (hasNextPage) {
console.log(`Next cursor: ${cursor}`);
}
} catch (error) {
console.error('Error fetching dynamic fields page:', error);
break;
}
}
return allFields;
}
// Analyze dynamic field types
async function analyzeDynamicFields(parentId) {
const fields = await getDynamicFieldsPaginated(parentId);
if (!fields || fields.length === 0) {
console.log('No dynamic fields found');
return null;
}
const typeAnalysis = fields.reduce((acc, field) => {
const fieldType = field.objectType;
if (!acc[fieldType]) {
acc[fieldType] = {
count: 0,
examples: []
};
}
acc[fieldType].count++;
if (acc[fieldType].examples.length < 3) {
acc[fieldType].examples.push({
name: field.name,
objectId: field.objectId
});
}
return acc;
}, {});
console.log('Dynamic Field Type Analysis:');
Object.entries(typeAnalysis).forEach(([type, info]) => {
console.log(` ${type}: ${info.count} fields`);
info.examples.forEach((example, i) => {
console.log(` Example ${i + 1}: ${JSON.stringify(example.name)} -> ${example.objectId}`);
});
});
return {
totalFields: fields.length,
typeDistribution: typeAnalysis,
fields: fields
};
}
// Dynamic field explorer
class DynamicFieldExplorer {
constructor(client) {
this.client = client;
}
async exploreObject(objectId, maxDepth = 3) {
return await this._exploreRecursive(objectId, 0, maxDepth);
}
async _exploreRecursive(objectId, currentDepth, maxDepth, visited = new Set()) {
if (currentDepth >= maxDepth || visited.has(objectId)) {
return { objectId, depth: currentDepth, truncated: true };
}
visited.add(objectId);
try {
// Get object info
const objectInfo = await this.client.getObject({
id: objectId,
options: { showType: true, showContent: true }
});
// Get dynamic fields
const dynamicFields = await this.client.getDynamicFields({
parentId: objectId
});
const exploration = {
objectId: objectId,
type: objectInfo.data?.type,
depth: currentDepth,
dynamicFields: [],
hasMorePages: dynamicFields.hasNextPage
};
// Recursively explore dynamic fields (up to depth limit)
if (currentDepth < maxDepth - 1) {
for (const field of dynamicFields.data) {
const fieldExploration = {
name: field.name,
type: field.objectType,
objectId: field.objectId,
children: await this._exploreRecursive(
field.objectId,
currentDepth + 1,
maxDepth,
visited
)
};
exploration.dynamicFields.push(fieldExploration);
}
} else {
exploration.dynamicFields = dynamicFields.data.map(field => ({
name: field.name,
type: field.objectType,
objectId: field.objectId,
truncated: true
}));
}
return exploration;
} catch (error) {
console.error(`Error exploring object ${objectId} at depth ${currentDepth}:`, error);
return {
objectId,
depth: currentDepth,
error: error.message
};
}
}
printExploration(exploration, indent = '') {
console.log(`${indent}Object: ${exploration.objectId}`);
console.log(`${indent}Type: ${exploration.type || 'Unknown'}`);
console.log(`${indent}Depth: ${exploration.depth}`);
if (exploration.error) {
console.log(`${indent}Error: ${exploration.error}`);
return;
}
if (exploration.truncated) {
console.log(`${indent}[Truncated - max depth reached]`);
return;
}
if (exploration.dynamicFields && exploration.dynamicFields.length > 0) {
console.log(`${indent}Dynamic Fields (${exploration.dynamicFields.length}):`);
exploration.dynamicFields.forEach((field, index) => {
console.log(`${indent} ${index + 1}. Name: ${JSON.stringify(field.name)}`);
console.log(`${indent} Type: ${field.type}`);
console.log(`${indent} Object ID: ${field.objectId}`);
if (field.children && !field.truncated) {
console.log(`${indent} Children:`);
this.printExploration(field.children, indent + ' ');
} else if (field.truncated) {
console.log(`${indent} [Truncated]`);
}
console.log('');
});
} else {
console.log(`${indent}No dynamic fields`);
}
if (exploration.hasMorePages) {
console.log(`${indent}[Has more pages - use pagination to see all fields]`);
}
}
}
// Dynamic field utilities
class DynamicFieldUtils {
constructor(client) {
this.client = client;
}
async findFieldByName(parentId, targetName) {
let cursor = null;
let hasNextPage = true;
while (hasNextPage) {
const result = await this.client.getDynamicFields({
parentId: parentId,
cursor: cursor,
limit: 50
});
const found = result.data.find(field => {
// Compare field names (could be complex objects)
return JSON.stringify(field.name) === JSON.stringify(targetName) ||
field.name.value === targetName ||
(typeof field.name === 'string' && field.name === targetName);
});
if (found) {
return found;
}
cursor = result.nextCursor;
hasNextPage = result.hasNextPage;
}
return null;
}
async findFieldsByType(parentId, targetType) {
const matchingFields = [];
let cursor = null;
let hasNextPage = true;
while (hasNextPage) {
const result = await this.client.getDynamicFields({
parentId: parentId,
cursor: cursor,
limit: 50
});
const matches = result.data.filter(field =>
field.objectType === targetType ||
field.type === targetType
);
matchingFields.push(...matches);
cursor = result.nextCursor;
hasNextPage = result.hasNextPage;
}
return matchingFields;
}
async getFieldValue(fieldObjectId) {
try {
const fieldObject = await this.client.getObject({
id: fieldObjectId,
options: {
showType: true,
showContent: true,
showBcs: true
}
});
return fieldObject.data;
} catch (error) {
console.error(`Error fetching field value for ${fieldObjectId}:`, error);
return null;
}
}
async getFieldsWithValues(parentId, limit = 10) {
const fields = await this.client.getDynamicFields({
parentId: parentId,
limit: limit
});
const fieldsWithValues = [];
for (const field of fields.data) {
const value = await this.getFieldValue(field.objectId);
fieldsWithValues.push({
...field,
value: value
});
}
return {
data: fieldsWithValues,
nextCursor: fields.nextCursor,
hasNextPage: fields.hasNextPage
};
}
}
// Usage examples
const parentObjectId = '0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f';
// Basic usage
const dynamicFields = await getDynamicFields(parentObjectId);
console.log('Dynamic fields:', dynamicFields);
// Analyze field types
const analysis = await analyzeDynamicFields(parentObjectId);
console.log('Field analysis:', analysis);
// Explore object hierarchy
const explorer = new DynamicFieldExplorer(client);
const exploration = await explorer.exploreObject(parentObjectId, 3);
console.log('\nObject Exploration:');
explorer.printExploration(exploration);
// Find specific fields
const utils = new DynamicFieldUtils(client);
const specificField = await utils.findFieldByName(parentObjectId, 'user_data');
console.log('Found field:', specificField);
// Get fields with values
const fieldsWithValues = await utils.getFieldsWithValues(parentObjectId, 5);
console.log('Fields with values:', fieldsWithValues);
import requests
import json
from typing import Dict, List, Any, Optional, Union
import time
class SuiDynamicFieldsClient:
def __init__(self, rpc_url: str):
self.rpc_url = rpc_url
def get_dynamic_fields(
self,
parent_id: str,
cursor: Optional[str] = None,
limit: Optional[int] = None
) -> Dict[str, Any]:
"""Get dynamic fields for a parent object"""
params = [parent_id]
if cursor is not None:
params.append(cursor)
if limit is not None:
params.append(limit)
elif limit is not None:
params.extend([None, limit])
payload = {
"jsonrpc": "2.0",
"method": "suix_getDynamicFields",
"params": params,
"id": 1
}
try:
response = requests.post(
self.rpc_url,
headers={'Content-Type': 'application/json'},
data=json.dumps(payload),
timeout=30
)
result = response.json()
if 'error' in result:
raise Exception(f"RPC Error: {result['error']}")
return result['result']
except Exception as e:
print(f"Error fetching dynamic fields: {e}")
return {'data': [], 'hasNextPage': False, 'nextCursor': None}
def get_all_dynamic_fields(self, parent_id: str, limit_per_page: int = 50) -> List[Dict[str, Any]]:
"""Get all dynamic fields using pagination"""
all_fields = []
cursor = None
has_next_page = True
while has_next_page:
result = self.get_dynamic_fields(parent_id, cursor, limit_per_page)
all_fields.extend(result.get('data', []))
cursor = result.get('nextCursor')
has_next_page = result.get('hasNextPage', False)
if has_next_page:
print(f"Fetched {len(result.get('data', []))} fields, total: {len(all_fields)}")
print(f"Total dynamic fields retrieved: {len(all_fields)}")
return all_fields
def get_object(self, object_id: str) -> Optional[Dict[str, Any]]:
"""Get object information"""
payload = {
"jsonrpc": "2.0",
"method": "sui_getObject",
"params": [
object_id,
{
"showType": True,
"showContent": True,
"showBcs": True
}
],
"id": 1
}
try:
response = requests.post(
self.rpc_url,
headers={'Content-Type': 'application/json'},
data=json.dumps(payload),
timeout=30
)
result = response.json()
if 'error' in result:
print(f"Error fetching object {object_id}: {result['error']}")
return None
return result['result']
except Exception as e:
print(f"Error fetching object {object_id}: {e}")
return None
class DynamicFieldAnalyzer:
def __init__(self, client: SuiDynamicFieldsClient):
self.client = client
def analyze_field_types(self, parent_id: str) -> Dict[str, Any]:
"""Analyze the types of dynamic fields"""
fields = self.client.get_all_dynamic_fields(parent_id)
if not fields:
return {'error': 'No dynamic fields found'}
type_analysis = {}
for field in fields:
field_type = field.get('objectType', 'Unknown')
if field_type not in type_analysis:
type_analysis[field_type] = {
'count': 0,
'examples': []
}
type_analysis[field_type]['count'] += 1
if len(type_analysis[field_type]['examples']) < 3:
type_analysis[field_type]['examples'].append({
'name': field.get('name'),
'objectId': field.get('objectId'),
'version': field.get('version')
})
return {
'totalFields': len(fields),
'typeDistribution': type_analysis,
'fields': fields
}
def find_fields_by_type(self, parent_id: str, target_type: str) -> List[Dict[str, Any]]:
"""Find all fields of a specific type"""
fields = self.client.get_all_dynamic_fields(parent_id)
matching_fields = []
for field in fields:
if (field.get('objectType') == target_type or
field.get('type') == target_type):
matching_fields.append(field)
return matching_fields
def find_field_by_name(self, parent_id: str, target_name: Union[str, Dict]) -> Optional[Dict[str, Any]]:
"""Find a field by its name"""
fields = self.client.get_all_dynamic_fields(parent_id)
for field in fields:
field_name = field.get('name')
# Handle different name formats
if isinstance(target_name, str):
if (field_name == target_name or
(isinstance(field_name, dict) and field_name.get('value') == target_name)):
return field
else:
if field_name == target_name:
return field
return None
class DynamicFieldExplorer:
def __init__(self, client: SuiDynamicFieldsClient):
self.client = client
def explore_object(self, object_id: str, max_depth: int = 3) -> Dict[str, Any]:
"""Explore an object and its dynamic fields recursively"""
return self._explore_recursive(object_id, 0, max_depth, set())
def _explore_recursive(
self,
object_id: str,
current_depth: int,
max_depth: int,
visited: set
) -> Dict[str, Any]:
"""Recursively explore object hierarchy"""
if current_depth >= max_depth or object_id in visited:
return {
'objectId': object_id,
'depth': current_depth,
'truncated': True
}
visited.add(object_id)
try:
# Get object info
object_info = self.client.get_object(object_id)
# Get dynamic fields
dynamic_fields_result = self.client.get_dynamic_fields(object_id)
dynamic_fields = dynamic_fields_result.get('data', [])
exploration = {
'objectId': object_id,
'type': object_info.get('data', {}).get('type') if object_info else None,
'depth': current_depth,
'dynamicFields': [],
'hasMorePages': dynamic_fields_result.get('hasNextPage', False)
}
# Recursively explore dynamic fields
if current_depth < max_depth - 1:
for field in dynamic_fields:
field_exploration = {
'name': field.get('name'),
'type': field.get('objectType'),
'objectId': field.get('objectId'),
'children': self._explore_recursive(
field.get('objectId'),
current_depth + 1,
max_depth,
visited
)
}
exploration['dynamicFields'].append(field_exploration)
else:
exploration['dynamicFields'] = [
{
'name': field.get('name'),
'type': field.get('objectType'),
'objectId': field.get('objectId'),
'truncated': True
}
for field in dynamic_fields
]
return exploration
except Exception as e:
return {
'objectId': object_id,
'depth': current_depth,
'error': str(e)
}
def print_exploration(self, exploration: Dict[str, Any], indent: str = ''):
"""Print the exploration results in a readable format"""
print(f"{indent}Object: {exploration['objectId']}")
print(f"{indent}Type: {exploration.get('type', 'Unknown')}")
print(f"{indent}Depth: {exploration['depth']}")
if 'error' in exploration:
print(f"{indent}Error: {exploration['error']}")
return
if exploration.get('truncated'):
print(f"{indent}[Truncated - max depth reached]")
return
dynamic_fields = exploration.get('dynamicFields', [])
if dynamic_fields:
print(f"{indent}Dynamic Fields ({len(dynamic_fields)}):")
for i, field in enumerate(dynamic_fields):
print(f"{indent} {i + 1}. Name: {field['name']}")
print(f"{indent} Type: {field['type']}")
print(f"{indent} Object ID: {field['objectId']}")
if 'children' in field and not field.get('truncated'):
print(f"{indent} Children:")
self.print_exploration(field['children'], indent + ' ')
elif field.get('truncated'):
print(f"{indent} [Truncated]")
print()
else:
print(f"{indent}No dynamic fields")
if exploration.get('hasMorePages'):
print(f"{indent}[Has more pages - use pagination to see all fields]")
class DynamicFieldUtils:
def __init__(self, client: SuiDynamicFieldsClient):
self.client = client
def get_fields_with_values(self, parent_id: str, limit: int = 10) -> Dict[str, Any]:
"""Get dynamic fields along with their values"""
fields_result = self.client.get_dynamic_fields(parent_id, limit=limit)
fields = fields_result.get('data', [])
fields_with_values = []
for field in fields:
field_object_id = field.get('objectId')
if field_object_id:
value = self.client.get_object(field_object_id)
field_with_value = field.copy()
field_with_value['value'] = value
fields_with_values.append(field_with_value)
return {
'data': fields_with_values,
'nextCursor': fields_result.get('nextCursor'),
'hasNextPage': fields_result.get('hasNextPage', False)
}
def create_field_summary(self, parent_id: str) -> Dict[str, Any]:
"""Create a comprehensive summary of dynamic fields"""
fields = self.client.get_all_dynamic_fields(parent_id)
if not fields:
return {'error': 'No dynamic fields found'}
summary = {
'parentId': parent_id,
'totalFields': len(fields),
'fieldTypes': {},
'namePatterns': {},
'examples': []
}
for field in fields:
# Count field types
field_type = field.get('objectType', 'Unknown')
summary['fieldTypes'][field_type] = summary['fieldTypes'].get(field_type, 0) + 1
# Analyze name patterns
field_name = field.get('name')
name_type = type(field_name).__name__
summary['namePatterns'][name_type] = summary['namePatterns'].get(name_type, 0) + 1
# Collect examples
if len(summary['examples']) < 5:
summary['examples'].append({
'name': field_name,
'type': field_type,
'objectId': field.get('objectId'),
'version': field.get('version')
})
return summary
# Usage examples
client = SuiDynamicFieldsClient('https://sui-mainnet.dwellir.com/YOUR_API_KEY')
# Example 1: Basic dynamic fields query
parent_object_id = '0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f'
print("Fetching dynamic fields...")
dynamic_fields = client.get_dynamic_fields(parent_object_id)
print(f"Found {len(dynamic_fields.get('data', []))} dynamic fields")
print(f"Has next page: {dynamic_fields.get('hasNextPage', False)}")
if dynamic_fields.get('data'):
print("\nFirst few fields:")
for i, field in enumerate(dynamic_fields['data'][:3]):
print(f" {i + 1}. Name: {field.get('name')}")
print(f" Type: {field.get('objectType')}")
print(f" Object ID: {field.get('objectId')}")
# Example 2: Analyze field types
print("\nAnalyzing field types...")
analyzer = DynamicFieldAnalyzer(client)
analysis = analyzer.analyze_field_types(parent_object_id)
if 'error' not in analysis:
print(f"Total fields: {analysis['totalFields']}")
print("Field type distribution:")
for field_type, info in analysis['typeDistribution'].items():
print(f" {field_type}: {info['count']} fields")
for example in info['examples'][:2]:
print(f" Example: {example['name']} -> {example['objectId']}")
# Example 3: Explore object hierarchy
print("\nExploring object hierarchy...")
explorer = DynamicFieldExplorer(client)
exploration = explorer.explore_object(parent_object_id, max_depth=2)
print("Object Exploration:")
explorer.print_exploration(exploration)
# Example 4: Find specific fields
print("\nSearching for specific fields...")
target_field = analyzer.find_field_by_name(parent_object_id, 'metadata')
if target_field:
print(f"Found metadata field: {target_field['objectId']}")
else:
print("Metadata field not found")
# Example 5: Get fields with values
print("\nFetching fields with values...")
utils = DynamicFieldUtils(client)
fields_with_values = utils.get_fields_with_values(parent_object_id, limit=3)
print(f"Retrieved {len(fields_with_values['data'])} fields with values:")
for field in fields_with_values['data']:
print(f" Field: {field.get('name')}")
print(f" Type: {field.get('objectType')}")
if field.get('value'):
print(f" Value type: {field['value'].get('data', {}).get('type', 'Unknown')}")
print()
# Example 6: Create comprehensive summary
print("Creating field summary...")
summary = utils.create_field_summary(parent_object_id)
if 'error' not in summary:
print(f"Parent Object: {summary['parentId']}")
print(f"Total Fields: {summary['totalFields']}")
print("Field Types:")
for field_type, count in summary['fieldTypes'].items():
print(f" {field_type}: {count}")
print("Name Patterns:")
for pattern, count in summary['namePatterns'].items():
print(f" {pattern}: {count}")
print("Examples:")
for example in summary['examples']:
print(f" {example['name']} ({example['type']}) -> {example['objectId']}")
Response Example​
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"data": [
{
"name": {
"type": "0x2::string::String",
"value": "user_profile"
},
"bcsName": "0x757365725f70726f66696c65",
"type": "DynamicField",
"objectType": "0x2::dynamic_field::Field<0x2::string::String, 0xa1b2c3::user::Profile>",
"objectId": "0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"version": "12345",
"digest": "AbCdEf123456789..."
},
{
"name": {
"type": "u64",
"value": "42"
},
"bcsName": "0x2a",
"type": "DynamicField",
"objectType": "0x2::dynamic_field::Field<u64, vector<u8>>",
"objectId": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"version": "67890",
"digest": "123456AbCdEf..."
}
],
"nextCursor": "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba",
"hasNextPage": true
}
}
Practical Examples​
Dynamic Field Manager​
class DynamicFieldManager {
constructor(client) {
this.client = client;
this.fieldCache = new Map();
}
async getFieldsStructure(parentId, useCache = true) {
const cacheKey = `fields_${parentId}`;
if (useCache && this.fieldCache.has(cacheKey)) {
return this.fieldCache.get(cacheKey);
}
const fields = await this.getAllDynamicFields(parentId);
const structure = {
parentId: parentId,
totalFields: fields.length,
fieldsByType: this.groupFieldsByType(fields),
fieldsByName: this.groupFieldsByName(fields),
metadata: {
lastUpdated: Date.now(),
versions: fields.map(f => parseInt(f.version))
}
};
if (useCache) {
this.fieldCache.set(cacheKey, structure);
}
return structure;
}
groupFieldsByType(fields) {
return fields.reduce((acc, field) => {
const type = field.objectType;
if (!acc[type]) {
acc[type] = [];
}
acc[type].push(field);
return acc;
}, {});
}
groupFieldsByName(fields) {
return fields.reduce((acc, field) => {
const nameKey = this.getFieldNameKey(field.name);
acc[nameKey] = field;
return acc;
}, {});
}
getFieldNameKey(name) {
if (typeof name === 'string') return name;
if (name && name.value) return name.value.toString();
return JSON.stringify(name);
}
async getAllDynamicFields(parentId) {
let allFields = [];
let cursor = null;
let hasNextPage = true;
while (hasNextPage) {
const result = await this.client.getDynamicFields({
parentId: parentId,
cursor: cursor,
limit: 50
});
allFields = allFields.concat(result.data);
cursor = result.nextCursor;
hasNextPage = result.hasNextPage;
}
return allFields;
}
}
Field Value Resolver​
class FieldValueResolver {
constructor(client) {
this.client = client;
this.valueCache = new Map();
}
async resolveFieldValues(parentId, fieldNames = []) {
const fields = await this.client.getDynamicFields({ parentId });
const resolved = {};
for (const field of fields.data) {
const nameKey = this.getFieldNameKey(field.name);
// If specific fields requested, filter
if (fieldNames.length > 0 && !fieldNames.includes(nameKey)) {
continue;
}
try {
const value = await this.getFieldValue(field.objectId);
resolved[nameKey] = {
field: field,
value: value,
resolved: true
};
} catch (error) {
resolved[nameKey] = {
field: field,
error: error.message,
resolved: false
};
}
}
return resolved;
}
async getFieldValue(fieldObjectId, useCache = true) {
if (useCache && this.valueCache.has(fieldObjectId)) {
return this.valueCache.get(fieldObjectId);
}
const fieldObject = await this.client.getObject({
id: fieldObjectId,
options: {
showType: true,
showContent: true,
showBcs: true
}
});
if (fieldObject && fieldObject.data) {
if (useCache) {
this.valueCache.set(fieldObjectId, fieldObject.data);
}
return fieldObject.data;
}
return null;
}
getFieldNameKey(name) {
if (typeof name === 'string') return name;
if (name && name.value !== undefined) return name.value.toString();
return JSON.stringify(name);
}
}
Dynamic Object Inspector​
class DynamicObjectInspector {
constructor(client) {
this.client = client;
}
async inspectObject(objectId, options = {}) {
const {
includeFieldValues = false,
maxFieldsToInspect = 20,
includeTypeAnalysis = true
} = options;
try {
// Get object info
const objectInfo = await this.client.getObject({
id: objectId,
options: { showType: true, showContent: true }
});
// Get dynamic fields
const fieldsResult = await this.client.getDynamicFields({
parentId: objectId,
limit: maxFieldsToInspect
});
const inspection = {
objectId: objectId,
objectType: objectInfo.data?.type,
objectVersion: objectInfo.data?.version,
dynamicFieldsCount: fieldsResult.data.length,
hasMoreFields: fieldsResult.hasNextPage,
fields: fieldsResult.data
};
if (includeTypeAnalysis) {
inspection.typeAnalysis = this.analyzeFieldTypes(fieldsResult.data);
}
if (includeFieldValues) {
inspection.fieldValues = {};
for (const field of fieldsResult.data.slice(0, 10)) { // Limit to first 10
try {
const value = await this.client.getObject({
id: field.objectId,
options: { showContent: true }
});
const nameKey = this.getFieldNameKey(field.name);
inspection.fieldValues[nameKey] = value.data;
} catch (error) {
console.warn(`Failed to get value for field ${field.objectId}:`, error);
}
}
}
return inspection;
} catch (error) {
return {
objectId: objectId,
error: error.message,
timestamp: Date.now()
};
}
}
analyzeFieldTypes(fields) {
const typeCount = {};
const nameTypes = {};
fields.forEach(field => {
// Count object types
const objType = field.objectType;
typeCount[objType] = (typeCount[objType] || 0) + 1;
// Analyze name types
const nameType = typeof field.name === 'object' ?
field.name.type || 'object' : typeof field.name;
nameTypes[nameType] = (nameTypes[nameType] || 0) + 1;
});
return {
objectTypes: typeCount,
nameTypes: nameTypes,
totalFields: fields.length,
uniqueTypes: Object.keys(typeCount).length
};
}
getFieldNameKey(name) {
if (typeof name === 'string') return name;
if (name && name.value !== undefined) return name.value.toString();
return JSON.stringify(name);
}
async compareObjects(objectIds) {
const comparisons = [];
for (const objectId of objectIds) {
const inspection = await this.inspectObject(objectId, {
includeTypeAnalysis: true,
includeFieldValues: false
});
comparisons.push(inspection);
}
return {
objects: comparisons,
comparison: {
totalObjects: comparisons.length,
averageFieldsPerObject: comparisons.reduce(
(sum, obj) => sum + (obj.dynamicFieldsCount || 0), 0
) / comparisons.length,
commonTypes: this.findCommonTypes(comparisons),
uniquePatterns: this.findUniquePatterns(comparisons)
}
};
}
findCommonTypes(comparisons) {
const allTypes = new Set();
comparisons.forEach(comp => {
if (comp.typeAnalysis) {
Object.keys(comp.typeAnalysis.objectTypes).forEach(type =>
allTypes.add(type)
);
}
});
const commonTypes = {};
allTypes.forEach(type => {
const count = comparisons.filter(comp =>
comp.typeAnalysis?.objectTypes[type] > 0
).length;
if (count > 1) {
commonTypes[type] = count;
}
});
return commonTypes;
}
findUniquePatterns(comparisons) {
const patterns = [];
comparisons.forEach(comp => {
if (comp.typeAnalysis) {
const uniqueTypes = Object.keys(comp.typeAnalysis.objectTypes).filter(
type => {
const appearsInOthers = comparisons.some(other =>
other !== comp && other.typeAnalysis?.objectTypes[type] > 0
);
return !appearsInOthers;
}
);
if (uniqueTypes.length > 0) {
patterns.push({
objectId: comp.objectId,
uniqueTypes: uniqueTypes
});
}
}
});
return patterns;
}
}
Error Handling​
Common errors and how to handle them:
async function safeDynamicFieldsQuery(parentId, options = {}) {
const { maxRetries = 3, retryDelay = 1000 } = options;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await client.getDynamicFields({
parentId: parentId,
limit: 50
});
if (!result || !result.data) {
throw new Error('Invalid response format');
}
return { success: true, data: result };
} catch (error) {
console.warn(`Dynamic fields query attempt ${attempt} failed:`, error.message);
// Handle specific error types
if (error.message.includes('Object not found')) {
return {
success: false,
error: 'OBJECT_NOT_FOUND',
message: 'Parent object does not exist or is not accessible'
};
}
if (error.message.includes('Invalid object ID')) {
return {
success: false,
error: 'INVALID_OBJECT_ID',
message: 'Invalid object ID format'
};
}
if (attempt === maxRetries) {
return {
success: false,
error: 'MAX_RETRIES_EXCEEDED',
message: error.message
};
}
await new Promise(resolve => setTimeout(resolve, retryDelay * attempt));
}
}
}
Best Practices​
- Use Pagination: Always handle pagination for objects with many dynamic fields
- Cache Results: Cache field information when exploring object hierarchies
- Handle Large Objects: Be mindful of objects with hundreds of dynamic fields
- Validate Field Names: Handle different field name formats (strings, objects, numbers)
- Implement Retries: Network requests can fail, implement retry logic
- Limit Depth: When exploring recursively, set reasonable depth limits
Related Methods​
- sui_getObject - Get detailed object information
- suix_getOwnedObjects - Get objects owned by address
- sui_multiGetObjects - Get multiple objects
Need help? Contact our support team or check the Sui documentation.