Skip to content

Commit 07cf8a5

Browse files
committed
BUG: fix to_json serialization for Period objects
1 parent f496acf commit 07cf8a5

File tree

4 files changed

+69
-0
lines changed

4 files changed

+69
-0
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,7 @@ I/O
788788
- Bug in :meth:`read_stata` where the missing code for double was not recognised for format versions 105 and prior (:issue:`58149`)
789789
- Bug in :meth:`set_option` where setting the pandas option ``display.html.use_mathjax`` to ``False`` has no effect (:issue:`59884`)
790790
- Bug in :meth:`to_excel` where :class:`MultiIndex` columns would be merged to a single row when ``merge_cells=False`` is passed (:issue:`60274`)
791+
- Bug in :meth:`to_json` period dtype was not being converted to string (:issue:`55490`)
791792

792793
Period
793794
^^^^^^

pandas/_libs/src/vendored/ujson/python/objToJSON.c

+7
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ int object_is_series_type(PyObject *obj);
6262
int object_is_index_type(PyObject *obj);
6363
int object_is_nat_type(PyObject *obj);
6464
int object_is_na_type(PyObject *obj);
65+
int object_is_offset_type(PyObject *obj);
6566

6667
typedef struct __NpyArrContext {
6768
PyObject *array;
@@ -927,6 +928,12 @@ static int Dir_iterNext(JSOBJ _obj, JSONTypeContext *tc) {
927928
continue;
928929
}
929930

931+
// Skip the 'base' attribute for BaseOffset objects
932+
if (object_is_offset_type(obj) && strcmp(attrStr, "base") == 0) {
933+
Py_DECREF(attr);
934+
continue;
935+
}
936+
930937
itemValue = PyObject_GetAttr(obj, attrName);
931938
if (itemValue == NULL) {
932939
PyErr_Clear();

pandas/_libs/src/vendored/ujson/python/ujson.c

+54
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ typedef struct {
7474
PyObject *type_index;
7575
PyObject *type_nat;
7676
PyObject *type_na;
77+
PyObject *type_offset;
7778
} modulestate;
7879

7980
#define modulestate(o) ((modulestate *)PyModule_GetState(o))
@@ -211,6 +212,26 @@ int object_is_na_type(PyObject *obj) {
211212
}
212213
return result;
213214
}
215+
216+
int object_is_offset_type(PyObject *obj) {
217+
PyObject *module = PyState_FindModule(&moduledef);
218+
if (module == NULL)
219+
return 0;
220+
modulestate *state = modulestate(module);
221+
if (state == NULL)
222+
return 0;
223+
PyObject *type_offset = state->type_offset;
224+
if (type_offset == NULL) {
225+
PyErr_Clear();
226+
return 0;
227+
}
228+
int result = PyObject_IsInstance(obj, type_offset);
229+
if (result == -1) {
230+
PyErr_Clear();
231+
return 0;
232+
}
233+
return result;
234+
}
214235
#else
215236
/* Used in objToJSON.c */
216237
int object_is_decimal_type(PyObject *obj) {
@@ -345,6 +366,27 @@ int object_is_na_type(PyObject *obj) {
345366
return result;
346367
}
347368

369+
int object_is_offset_type(PyObject *obj) {
370+
PyObject *module = PyImport_ImportModule("pandas._libs.tslibs.offsets");
371+
if (module == NULL) {
372+
PyErr_Clear();
373+
return 0;
374+
}
375+
PyObject *type_offset = PyObject_GetAttrString(module, "BaseOffset");
376+
if (type_offset == NULL) {
377+
Py_DECREF(module);
378+
PyErr_Clear();
379+
return 0;
380+
}
381+
int result = PyObject_IsInstance(obj, type_offset);
382+
if (result == -1) {
383+
Py_DECREF(module);
384+
Py_DECREF(type_offset);
385+
PyErr_Clear();
386+
return 0;
387+
}
388+
return result;
389+
}
348390
#endif
349391

350392
static int module_traverse(PyObject *m, visitproc visit, void *arg) {
@@ -354,6 +396,7 @@ static int module_traverse(PyObject *m, visitproc visit, void *arg) {
354396
Py_VISIT(modulestate(m)->type_index);
355397
Py_VISIT(modulestate(m)->type_nat);
356398
Py_VISIT(modulestate(m)->type_na);
399+
Py_VISIT(modulestate(m)->type_offset);
357400
return 0;
358401
}
359402

@@ -364,6 +407,7 @@ static int module_clear(PyObject *m) {
364407
Py_CLEAR(modulestate(m)->type_index);
365408
Py_CLEAR(modulestate(m)->type_nat);
366409
Py_CLEAR(modulestate(m)->type_na);
410+
Py_CLEAR(modulestate(m)->type_offset);
367411
return 0;
368412
}
369413

@@ -434,6 +478,16 @@ PyMODINIT_FUNC PyInit_json(void) {
434478
} else {
435479
PyErr_Clear();
436480
}
481+
482+
PyObject *mod_offset = PyImport_ImportModule("pandas._libs.tslibs.offsets");
483+
if (mod_offset) {
484+
PyObject *type_offset = PyObject_GetAttrString(mod_offset, "BaseOffset");
485+
assert(type_offset != NULL);
486+
modulestate(module)->type_offset = type_offset;
487+
488+
Py_DECREF(mod_offset);
489+
}
490+
437491
#endif
438492

439493
/* Not vendored for now

pandas/tests/io/json/test_ujson.py

+7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import pytest
1313

1414
import pandas._libs.json as ujson
15+
from pandas._libs.tslibs import offsets
1516
from pandas.compat import IS64
1617

1718
from pandas import (
@@ -1041,3 +1042,9 @@ def test_encode_periodindex(self):
10411042
p = PeriodIndex(["2022-04-06", "2022-04-07"], freq="D")
10421043
df = DataFrame(index=p)
10431044
assert df.to_json() == "{}"
1045+
1046+
def test_to_json_with_period(self):
1047+
# GH 55490
1048+
offset = offsets.YearEnd(2021)
1049+
result = ujson.ujson_dumps(offset)
1050+
assert "base" not in result

0 commit comments

Comments
 (0)