Skip to content

Conversation

@edis-uipath
Copy link
Contributor

@edis-uipath edis-uipath commented Jan 28, 2026

Summary

This PR introduces new JSON serialization utilities for converting Python objects to JSON formats. The main additions are:

  • serialize_defaults() - A default handler function for json.dumps() that handles Pydantic models, dataclasses, enums, datetime objects, and other complex types
  • serialize_json() - A convenience wrapper that combines json.dumps() with serialize_defaults()

This PR consolidates existing serialization logic: The previously private _simple_serialize_defaults() function from tracing/_utils.py has been moved to a new public location at uipath.core.utils.serialization.json, made public (removed underscore prefix), renamed to serialize_defaults(), enhanced with a convenience wrapper serialize_json(), and the tracing module has been refactored to use the new centralized implementation.

What Changed

New Package Structure

src/uipath/core/utils/serialization/
├── __init__.py          # Exports serialize_defaults and serialize_json
└── json.py              # JSON serialization utilities

New Files

  • src/uipath/core/utils/serialization/__init__.py - Package exports
  • src/uipath/core/utils/serialization/json.py - Public serialization utilities with comprehensive docstrings
    • serialize_defaults() - Default handler for json.dumps()
    • serialize_json() - Convenience wrapper combining json.dumps() + serialize_defaults()
  • tests/utils/__init__.py - Test package
  • tests/utils/test_serialization.py - 35 comprehensive test cases covering all serialization scenarios

Refactored Files

  • src/uipath/core/utils/__init__.py - Now exports serialize_defaults and serialize_json
  • src/uipath/core/tracing/_utils.py - Removed duplicate _simple_serialize_defaults() function, now imports and uses serialize_json() from the new utils module. Removed unnecessary imports (dataclasses, datetime, timezone, enum, zoneinfo, pydantic, cast). Updated format_args_for_trace_json() and format_object_for_trace_json() to use serialize_json().

Key Features

The serialize_defaults() function handles:

  • ✅ Pydantic models (v1 and v2) → dict
  • ✅ Pydantic model classes → schema representation
  • ✅ Dataclasses → dict
  • ✅ Enums → their underlying value (recursively)
  • ✅ Datetime objects → ISO format strings
  • ✅ Timezone objects (timezone, ZoneInfo) → timezone names
  • ✅ Sets and tuples → list
  • ✅ Named tuples → dict
  • ✅ Objects with to_dict() or dict() methods
  • ✅ Native JSON types (dict, list, str, int, float, bool, None) → pass through
  • ✅ Unknown objects → str() fallback

The serialize_json() convenience function:

  • ✅ Wraps json.dumps(obj, default=serialize_defaults)
  • ✅ Provides a simpler API for common use case
  • ✅ Returns JSON string directly

How It Works

Using serialize_defaults() as default handler

import json
from uipath.core.utils.serialization import serialize_defaults

result = json.dumps(data, default=serialize_defaults)

When json.dumps() encounters a non-JSON-serializable type, it calls serialize_defaults() to convert it.

Using serialize_json() convenience function

from uipath.core.utils.serialization import serialize_json

result = serialize_json(data)  # Much simpler!

This is equivalent to json.dumps(data, default=serialize_defaults).

How json.dumps() with default Works

When you call json.dumps(data, default=serialize_defaults), here's what happens:

  1. json.dumps() starts serializing - It processes the data structure
  2. For each value encountered:
    • If it's a native JSON type (dict, list, str, int, float, bool, None):
      • ✓ Serialize it normally
    • If it's NOT a native JSON type (Pydantic model, datetime, enum, etc.):
      • ⚠️ Call serialize_defaults(that_object)
      • ✓ Use the returned value instead
      • ♻️ Continue serializing recursively

Example Flow

from datetime import datetime
from pydantic import BaseModel
from uipath.core.utils.serialization import serialize_json

class User(BaseModel):
    name: str
    created_at: datetime

data = {
    "user": User(name="Alice", created_at=datetime(2024, 1, 15)),
    "count": 42,
}

result = serialize_json(data)

Internal processing:

1. json.dumps() sees dict → ✓ OK
2. Processes key "user" → User (Pydantic) → ⚠️ Unknown type!
3. Calls: serialize_defaults(User(...))
4. Returns: {"name": "Alice", "created_at": datetime(...)}
5. json.dumps() now has a dict → ✓ OK
6. Processes "created_at" → datetime → ⚠️ Unknown type!
7. Calls: serialize_defaults(datetime(...))
8. Returns: "2024-01-15T10:00:00"
9. json.dumps() now has a string → ✓ OK
10. Processes "count" → 42 (int) → ✓ OK
11. Done!

Result:

{
  "user": {
    "name": "Alice",
    "created_at": "2024-01-15T10:00:00"
  },
  "count": 42
}

Test Coverage

All 35 tests use serialize_json() and verify the final deserialized JSON structure:

  • Primitives & None - Basic types pass through unchanged
  • Pydantic Models - Simple, nested, with None exclusion, model classes
  • Dataclasses - Simple, nested, class fallback
  • Enums - String values, int values, mixed types, recursive serialization
  • Datetime & Timezone - datetime, timezone.utc, ZoneInfo (with skipif for environments without tzdata)
  • Collections - Sets, tuples, empty collections
  • Complex Structures:
    • Dict of Pydantic models
    • Dict of dataclasses
    • List of Pydantic models
    • List of dataclasses
    • List of normal classes
    • List of mixed types (Pydantic + dataclass + normal class + primitives)
    • Nested lists of mixed types (7 different sublists)
  • Special Cases - Named tuples, to_dict() method, dict() method (v1), str() fallback

Usage Examples

Basic Usage with serialize_json()

from datetime import datetime
from pydantic import BaseModel
from uipath.core.utils.serialization import serialize_json

class Task(BaseModel):
    name: str
    created: datetime
    priority: int

task = Task(name="Review PR", created=datetime(2024, 1, 15, 10, 30), priority=1)

# Simple one-liner!
json_str = serialize_json(task)
# '{"name": "Review PR", "created": "2024-01-15T10:30:00", "priority": 1}'

Advanced Usage with serialize_defaults()

import json
from uipath.core.utils.serialization import serialize_defaults

# When you need json.dumps() options
json_str = json.dumps(task, default=serialize_defaults, indent=2)

Design Decisions

  1. Public API - Removed underscore prefix to make it a public utility
  2. Centralized location - Moved from tracing-specific utils to core utils for reusability
  3. Package structure - Created serialization/ package for future expansion (e.g., XML, YAML, etc.)
  4. File naming - Named file json.py to clearly indicate JSON-specific serialization
  5. Function naming - Renamed to serialize_defaults() (removed "simple" prefix) as it's no longer internal
  6. Convenience wrapper - Added serialize_json() for the common use case (cleaner than repeated json.dumps(..., default=...))
  7. Pass-through for native types - Includes list and dict to avoid unnecessary conversions
  8. Recursive enum handling - Enums with complex values are recursively serialized
  9. Named tuple behavior - Python's json encoder treats namedtuples as regular tuples, so they serialize as lists rather than dicts
  10. Comprehensive testing - Every test uses serialize_json() to ensure real-world usage works
  11. Code deduplication - Refactored tracing module to use serialize_json() directly

Impact

  • Before: Serialization logic duplicated in tracing/_utils.py (private function)
  • After: Centralized public API in uipath.core.utils.serialization with comprehensive tests
  • Benefits:
    • Single source of truth for JSON serialization
    • Reusable across the entire codebase
    • Well-tested with 35 test cases
    • Cleaner imports in tracing module
    • Consistent serialization behavior
    • Simpler API with serialize_json() wrapper

Stats

  • +787 additions / -55 deletions
  • Net gain: +732 lines (includes comprehensive documentation and tests)
  • Net code reduction: -53 lines of duplicate serialization logic

Related

This function consolidates serialization logic previously only available in tracing utilities and will be used across the codebase for consistent JSON serialization of complex Python objects.

@edis-uipath edis-uipath force-pushed the feature/basic-serialization-method branch 2 times, most recently from 1c43482 to 057b70c Compare January 28, 2026 11:36
@edis-uipath edis-uipath force-pushed the feature/basic-serialization-method branch 5 times, most recently from 0ecbdd3 to ace15d4 Compare January 28, 2026 12:28
@edis-uipath edis-uipath force-pushed the feature/basic-serialization-method branch from ace15d4 to b6f71ac Compare January 28, 2026 12:32
@edis-uipath edis-uipath merged commit 42d8f3e into main Jan 28, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants