Repository: apache/airflow
File: task-sdk/src/airflow/sdk/execution_time/task_runner.py (Python)

Code before:
    def sort_dict_recursively(obj: Any) -> Any:
        """Recursively sort dictionaries to ensure consistent ordering."""
        if isinstance(obj, dict):
            return {k: sort_dict_recursively(v) for k, v in sorted(obj.items())}
        if isinstance(obj, list):
            return [sort_dict_recursively(item) for item in obj]
        if isinstance(obj, tuple):
            return tuple(sort_dict_recursively(item) for item in obj)
        return obj

    def to_serializable(obj: Any) -> Any:
        """Convert objects to JSON serializable types."""
        if obj is None or isinstance(obj, (str, int, float, bool)):
            return obj

        if isinstance(obj, dict):
            return {k: to_serializable(v) for k, v in obj.items()}

        if isinstance(obj, (list, tuple)):
            return [to_serializable(item) for item in obj]

        # Some objects like Kubernetes ones or boto3 ones have to_dict defined, try using them if they exist
        if hasattr(obj, "to_dict"):
            return obj.to_dict()

        return obj

    max_length = conf.getint("core", "max_templated_field_length")

    if not is_jsonable(template_field):
        try:
            serialized = template_field.serialize()
        except AttributeError:
            # check if these objects can be converted to JSON serializable types
            try:
                serialized = json.dumps(to_serializable(template_field))
            except (TypeError, ValueError):
                # fall back to string representation if not
                serialized = str(template_field)
        if len(serialized) > max_length:
            rendered = redact(serialized, name)
            return (
                "Truncated. You can change this behaviour in [core]max_templated_field_length. "
                f"{rendered[: max_length - 79]!r}... "
            )
        return serialized
    if not template_field and not isinstance(template_field, tuple):
        # Avoid unnecessary serialization steps for empty fields unless they are tuples
        # and need to be converted to lists
        return template_field
    template_field = translate_tuples_to_lists(template_field)

Review comment (suggestion):
I wonder if it’d be a good idea to use `default` instead. Something like…

```python
def _fallback_serialization(obj):
    # Some objects like Kubernetes ones or boto3 ones have to_dict defined, try using them if they exist
    if hasattr(obj, "to_dict"):
        return obj.to_dict()
    raise TypeError(f"cannot serialize {obj}")

try:
    serialized = json.dumps(template_field, default=_fallback_serialization)
except (TypeError, ValueError):
    serialized = str(template_field)
```

Code after:
        return obj

    def sort_dict_recursively(obj: Any) -> Any:
        """Recursively sort dictionaries to ensure consistent ordering."""
        if isinstance(obj, dict):
            return {k: sort_dict_recursively(v) for k, v in sorted(obj.items())}
        if isinstance(obj, list):
            return [sort_dict_recursively(item) for item in obj]
        if isinstance(obj, tuple):
            return tuple(sort_dict_recursively(item) for item in obj)
        return obj

    def _fallback_serialization(obj):
        """Serialize objects with to_dict() method (eg: k8s objects) for json.dumps() default parameter."""
        if hasattr(obj, "to_dict"):
            return obj.to_dict()
        raise TypeError(f"cannot serialize {obj}")

    max_length = conf.getint("core", "max_templated_field_length")

    if not is_jsonable(template_field):
        try:
            serialized = template_field.serialize()
        except AttributeError:
            # check if these objects can be converted to JSON serializable types
            try:
                serialized = json.dumps(template_field, default=_fallback_serialization)
            except (TypeError, ValueError):
                # fall back to string representation if not
                serialized = str(template_field)
        if len(serialized) > max_length:
            rendered = redact(serialized, name)
            return (
                "Truncated. You can change this behaviour in [core]max_templated_field_length. "
                f"{rendered[: max_length - 79]!r}... "
            )
        return serialized
    if not template_field and not isinstance(template_field, tuple):
        # Avoid unnecessary serialization steps for empty fields unless they are tuples
        # and need to be converted to lists
        return template_field
    template_field = translate_tuples_to_lists(template_field)
    # Sort dictionaries recursively to ensure consistent string representation
    # This prevents hash inconsistencies when dict ordering varies
    if isinstance(template_field, dict):
        template_field = sort_dict_recursively(template_field)
    serialized = str(template_field)
    if len(serialized) > max_length:
        rendered = redact(serialized, name)
        return (
            "Truncated. You can change this behaviour in [core]max_templated_field_length. "