Fix JSON serialization error in Tabulator with mixed NaT datetime columns - #8491
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8491 +/- ##
==========================================
- Coverage 86.18% 86.06% -0.12%
==========================================
Files 349 349
Lines 55048 55058 +10
==========================================
- Hits 47444 47388 -56
- Misses 7604 7670 +66 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
This seems like a lot of computation for an edge case. |
By that time any NaNs should have been converted to an internal, JSON-compatible representation by Bokeh, e.g.: In [1]: from bokeh.core.serialization import Serializer
In [2]: s = Serializer()
In [3]: s.encode(float("nan"))
Out[3]: {'type': 'number', 'value': 'nan'}NaNs in ndarrays and similar are also handled. So either this analysis is wrong or there's a bug somewhere in Bokeh. |
@hoxbro Removed the heavy computation. The fix is now just correcting the Serializer registration key, which is O(1) and happens once per Tabulator instance. |
|
That looks much better. Can you add a test. You should verify that the tests fails before and works after the fix. |
@mattpap Good catch, you’re right that Bokeh’s Serializer already knows how to handle NaNs. The problem turned out to be on the Panel side: it was calling |
|
@mattpap Actually could you clarify
That was my thinking too but it seems like Guessing the internals of array data is untouched by the serializer. |
This is clearly a bug or some misguided attempt at performance optimization (a bug anyway). This should have been encoded the same way as any list would have been. |
@hoxbro @philippjfr I’ve added a regression test for this. In the screen recording I first reverted back to the old code and the test raised the ValueError, then I reapplied the fix and the test passed. So the fix is working and the issue is resolved now. 2026-03-09.14-42-20.mp4 |
|
LGTM, but can you update the first post with the new findings? |
@hoxbro Done, updated the PR description. ✅ |
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Description
Fixes a bug where
pn.widgets.Tabulatorcrashes withValueError: Out of range float values are not JSON compliantwhen a datetime column contains a mix ofpd.NaTand valid values after calling.dt.date.While investigating, I found that the problem wasn’t in Bokeh’s NaN handling itself but in how Panel registers a custom encoder for
pd.NaT. Panel was callingSerializer.register(pd.NaT, ...), but Bokeh’sSerializer._encode()looks up encoders bytype(obj), sotype(pd.NaT)(NaTType) never matched that registration. As a result,pd.NaTfell through to the default datetime path (convert_datetime_type), which returned a rawfloat('nan'). That raw NaN then ended up in the payload and causedserialize_jsonto raise the JSON error.Problem:
Mixed
pd.NaT/datetime columns ended up sending rawfloat('nan')through Bokeh’s JSON encoder because the custom encoder forpd.NaTwas registered on the instance instead of its type.Solution:
Register the encoder on
type(pd.NaT)so Bokeh’sSerializercan actually find and apply it:This is effectively a one‑word change (
pd.NaT→type(pd.NaT)) and keeps the rest of the Tabulator code unchanged. No per‑column or per‑row scanning is needed anymore.Example:
Fixes #7803
How Has This Been Tested?
Added a regression test that exercises the same code path as the original failure:
Locally verified:
Serializer.register(pd.NaT, ...)code, this test raises theValueError.Serializer.register(type(pd.NaT), ...), the same test passes.Also re-checked that:
AI Disclosure
Tools: Gemini 3 Flash – Used to analyze the bug, identify the root cause, and refine the implementation and tests.
Checklist