Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Unreleased

### Fixed
- Fixed `TypeError: Type is not JSON serializable: Timestamp` when a figure contains datetime-like values such as a `pandas` `Timestamp`; these now serialize to ISO strings [[#458](https://github.com/plotly/Kaleido/issues/458)]

## v1.3.0

### Added
Expand Down
2 changes: 2 additions & 0 deletions src/py/kaleido/_kaleido_tab/_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def _orjson_default(obj):
"""Fallback for types orjson can't handle natively (e.g. NumPy string arrays)."""
if isinstance(obj, Decimal):
return float(obj)
if hasattr(obj, "isoformat"): # datetime-like, e.g. pandas Timestamp (#458)
return obj.isoformat()
if hasattr(obj, "tolist"):
return obj.tolist()
raise TypeError(f"Type is not JSON serializable: {type(obj).__name__}")
Expand Down
31 changes: 31 additions & 0 deletions src/py/tests/test_orjson_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import datetime
from decimal import Decimal

import numpy as np
import orjson
import pandas as pd

from kaleido._kaleido_tab._tab import _orjson_default


def test_orjson_default_handles_datetime_like():
# A pandas Timestamp has no ``.tolist()`` and used to raise TypeError (#458);
# _orjson_default should fall back to an ISO string, like Plotly's encoder.
ts = pd.Timestamp("2026-06-03 12:00:00")
assert _orjson_default(ts) == ts.isoformat()

a_date = datetime.date(2026, 1, 2)
assert _orjson_default(a_date) == a_date.isoformat()

# A figure spec carrying a Timestamp now round-trips through orjson.
spec = {"x": [ts]}
dumped = orjson.dumps(
spec, default=_orjson_default, option=orjson.OPT_SERIALIZE_NUMPY
)
assert ts.isoformat().encode() in dumped

# Existing fallbacks are unaffected.
decimal_value = Decimal("1.5")
assert _orjson_default(decimal_value) == float(decimal_value)
array_values = [1, 2, 3]
assert _orjson_default(np.array(array_values)) == array_values