fix: serialize NaT in NumPy datetime64 arrays as JSON null by senanurcetin · Pull Request #5831 · plotly/plotly.py · GitHub
Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
4 changes: 3 additions & 1 deletion _plotly_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,9 @@ def encode_as_numpy(obj):
return float("nan")
elif isinstance(obj, numpy.ndarray) and obj.dtype.kind == "M":
try:
return numpy.datetime_as_string(obj).tolist()
return numpy.where(
numpy.isnat(obj), None, numpy.datetime_as_string(obj)
).tolist()
except TypeError:
pass

Expand Down
34 changes: 26 additions & 8 deletions plotly/io/_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ def _safe(json_str, _swap):
return out


def _has_nat_datetime_array(obj, np):
"""Return whether nested figure data contain missing NumPy datetimes."""
if np is None:
return False
if isinstance(obj, np.ndarray):
return obj.dtype.kind == "M" and bool(np.isnat(obj).any())
if isinstance(obj, dict):
return any(_has_nat_datetime_array(value, np) for value in obj.values())
if isinstance(obj, (list, tuple)):
return any(_has_nat_datetime_array(value, np) for value in obj)
return False


def to_json_plotly(plotly_object, pretty=False, engine=None):
"""
Convert a plotly/Dash object to a JSON string representation
Expand Down Expand Up @@ -153,13 +166,16 @@ def to_json_plotly(plotly_object, pretty=False, engine=None):
except AttributeError:
pass

# Try without cleaning
try:
return _safe(
orjson.dumps(plotly_object, option=opts).decode("utf8"), _swap_orjson
)
except TypeError:
pass
# The fast NumPy serializer can emit "NaT" or a spurious date for NaT.
# Normalize only when needed, preserving the fast path otherwise.
if not _has_nat_datetime_array(plotly_object, modules["np"]):
try:
return _safe(
orjson.dumps(plotly_object, option=opts).decode("utf8"),
_swap_orjson,
)
except TypeError:
pass

cleaned = clean_to_json_compatible(
plotly_object,
Expand Down Expand Up @@ -514,7 +530,9 @@ def clean_to_json_compatible(obj, **kwargs):
return np.ascontiguousarray(obj)
elif obj.dtype.kind == "M":
# datetime64 array
return np.datetime_as_string(obj).tolist()
return np.where(
np.isnat(obj), None, np.datetime_as_string(obj)
).tolist()
elif obj.dtype.kind == "U":
return obj.tolist()
elif obj.dtype.kind == "O":
Expand Down
33 changes: 33 additions & 0 deletions tests/test_io/test_to_from_plotly_json.py