-
Notifications
You must be signed in to change notification settings - Fork 105
Description
Description
JsonObject subclasses dict but when wrapping arrays or scalars, it returns early from __init__ without calling super().__init__(). This leaves the dict parent empty, causing standard Python operations to silently return wrong results.
Reproduction
from google.cloud.spanner_v1 import JsonObject
import json
val = JsonObject([{"id": "m1", "content": "hello"}])
print(isinstance(val, dict)) # True — it IS a dict subclass
print(len(val)) # 0 — expected 1
print(bool(val)) # False — expected True
print(list(val)) # [] — expected the list contents
print(json.dumps(val)) # "{}" — expected the JSON array
print(val.serialize()) # '[{"content":"hello","id":"m1"}]' — correctRoot cause
In data_types.py, the array and scalar paths return early without initializing the dict:
def __init__(self, *args, **kwargs):
self._is_array = len(args) and isinstance(args[0], (list, tuple))
if self._is_array:
self._array_value = args[0]
return # ← dict parent never initialized
if self._is_scalar_value:
self._simple_value = args[0]
return # ← same issueThe dict path (JSON objects) works correctly because it falls through to super().__init__().
Impact
Any code that receives a JsonObject wrapping an array and uses standard Python operations (len(), bool(), for x in val, val or default, json.dumps()) gets silently wrong results. This is particularly dangerous because isinstance(val, dict) returns True, so type checks pass but the object doesn't behave like one.
The .serialize() method works correctly for all variants because it reads from the internal _array_value/_simple_value attributes directly.
Suggested fix
Override __len__, __bool__, __iter__, and __getitem__ to delegate to the internal storage for array/scalar variants. Or stop subclassing dict since the array/scalar variants don't use it.
Version
Tested on 3.55.0, confirmed identical source in 3.61.0 (latest).