Skip to content

Commit b700991

Browse files
committed
BUG: Attributes skipped when serialising plain Python objects to JSON (pandas-dev#42768)
1 parent 544ebd2 commit b700991

File tree

3 files changed

+30
-28
lines changed

3 files changed

+30
-28
lines changed

doc/source/whatsnew/v1.3.2.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Bug fixes
3636
- :meth:`.Styler.hide_columns` now hides the index name header row as well as column headers (:issue:`42101`)
3737
- :meth:`.Styler.set_sticky` has amended CSS to control the column/index names and ensure the correct sticky positions (:issue:`42537`)
3838
- Bug in de-serializing datetime indexes in PYTHONOPTIMIZED mode (:issue:`42866`)
39+
- Some attributes were skipped when serialising plain Python objects to JSON (:issue:`42768`, :issue:`33043`)
3940
-
4041

4142
.. ---------------------------------------------------------------------------

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

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -882,37 +882,33 @@ void Dir_iterEnd(JSOBJ Py_UNUSED(obj), JSONTypeContext *tc) {
882882

883883
int Dir_iterNext(JSOBJ _obj, JSONTypeContext *tc) {
884884
PyObject *obj = (PyObject *)_obj;
885-
PyObject *itemValue = GET_TC(tc)->itemValue;
886-
PyObject *itemName = GET_TC(tc)->itemName;
887-
PyObject *attr;
888-
PyObject *attrName;
889-
char *attrStr;
890885

891886
if (PyErr_Occurred() || ((JSONObjectEncoder *)tc->encoder)->errorMsg) {
892887
return 0;
893888
}
894889

895-
if (itemValue) {
890+
if (GET_TC(tc)->itemValue) {
896891
Py_DECREF(GET_TC(tc)->itemValue);
897-
GET_TC(tc)->itemValue = itemValue = NULL;
892+
GET_TC(tc)->itemValue = NULL;
898893
}
899894

900-
if (itemName) {
895+
if (GET_TC(tc)->itemName) {
901896
Py_DECREF(GET_TC(tc)->itemName);
902-
GET_TC(tc)->itemName = itemName = NULL;
897+
GET_TC(tc)->itemName = NULL;
903898
}
904899

905900
for (; GET_TC(tc)->index < GET_TC(tc)->size; GET_TC(tc)->index++) {
906-
attrName = PyList_GET_ITEM(GET_TC(tc)->attrList, GET_TC(tc)->index);
907-
attr = PyUnicode_AsUTF8String(attrName);
908-
attrStr = PyBytes_AS_STRING(attr);
901+
PyObject *attrName =
902+
PyList_GET_ITEM(GET_TC(tc)->attrList, GET_TC(tc)->index);
903+
PyObject *attr = PyUnicode_AsUTF8String(attrName);
904+
char *attrStr = PyBytes_AS_STRING(attr);
909905

910906
if (attrStr[0] == '_') {
911907
Py_DECREF(attr);
912908
continue;
913909
}
914910

915-
itemValue = PyObject_GetAttr(obj, attrName);
911+
PyObject *itemValue = PyObject_GetAttr(obj, attrName);
916912
if (itemValue == NULL) {
917913
PyErr_Clear();
918914
Py_DECREF(attr);
@@ -925,25 +921,15 @@ int Dir_iterNext(JSOBJ _obj, JSONTypeContext *tc) {
925921
continue;
926922
}
927923

928-
GET_TC(tc)->itemName = itemName;
924+
GET_TC(tc)->itemName = attr;
929925
GET_TC(tc)->itemValue = itemValue;
930926
GET_TC(tc)->index++;
931-
932-
itemName = attr;
933-
break;
934-
}
935-
936-
if (itemName == NULL) {
937-
GET_TC(tc)->index = GET_TC(tc)->size;
938-
GET_TC(tc)->itemValue = NULL;
939-
return 0;
927+
return 1;
940928
}
941929

942-
GET_TC(tc)->itemName = itemName;
943-
GET_TC(tc)->itemValue = itemValue;
944-
GET_TC(tc)->index++;
945-
946-
return 1;
930+
GET_TC(tc)->index = GET_TC(tc)->size;
931+
GET_TC(tc)->itemValue = NULL;
932+
return 0;
947933
}
948934

949935
JSOBJ Dir_iterGetValue(JSOBJ Py_UNUSED(obj), JSONTypeContext *tc) {

pandas/tests/io/json/test_ujson.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,21 @@ def my_obj_handler(_):
720720
ujson.encode(obj_list, default_handler=str)
721721
)
722722

723+
def test_encode_object(self):
724+
# Keys should be all non-callable non-underscore attributes, see GH-42768
725+
class _TestObject:
726+
def __init__(self, a, b, _c, d):
727+
self.a = a
728+
self.b = b
729+
self._c = _c
730+
self.d = d
731+
732+
def e(self):
733+
return 5
734+
735+
test_object = _TestObject(a=1, b=2, _c=3, d=4)
736+
assert ujson.decode(ujson.encode(test_object)) == {"a": 1, "b": 2, "d": 4}
737+
723738

724739
class TestNumpyJSONTests:
725740
@pytest.mark.parametrize("bool_input", [True, False])

0 commit comments

Comments
 (0)