Skip to content
Closed
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
61 changes: 37 additions & 24 deletions Lib/json/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,37 @@ def _iterencode_list(lst, _current_indent_level):
del markers[markerid]

def _iterencode_dict(dct, _current_indent_level):
def _coerce_key(key):
if isinstance(key, str):
return key
# JavaScript is weakly typed for these, so it makes sense to
# also allow them. Many encoders seem to do something like this.
if isinstance(key, float):
# see comment for int/float in _make_iterencode
return _floatstr(key)
if key is True:
return 'true'
if key is False:
return 'false'
if key is None:
return 'null'
if isinstance(key, int):
# see comment for int/float in _make_iterencode
return int.__repr__(key)

if _skipkeys:
return None
else:
raise TypeError(f'keys must be str, int, float, bool or None, '
f'not {key.__class__.__name__}')

def _coerce_items(items):
for (k,v) in items:
k = _coerce_key(k) # Coerce or throw
if k is None:
continue
yield (k,v)

if not dct:
yield '{}'
return
Expand All @@ -342,32 +373,14 @@ def _iterencode_dict(dct, _current_indent_level):
newline_indent = None
item_separator = _item_separator
first = True

# Coerce keys to strings
items = _coerce_items(dct.items())

if _sort_keys:
items = sorted(dct.items())
else:
items = dct.items()
items = sorted(items)

for key, value in items:
if isinstance(key, str):
pass
# JavaScript is weakly typed for these, so it makes sense to
# also allow them. Many encoders seem to do something like this.
elif isinstance(key, float):
# see comment for int/float in _make_iterencode
key = _floatstr(key)
elif key is True:
key = 'true'
elif key is False:
key = 'false'
elif key is None:
key = 'null'
elif isinstance(key, int):
# see comment for int/float in _make_iterencode
key = int.__repr__(key)
elif _skipkeys:
continue
else:
raise TypeError(f'keys must be str, int, float, bool or None, '
f'not {key.__class__.__name__}')
if first:
first = False
if newline_indent is not None:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_json/test_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_encode_truefalse(self):
'{"false": true, "true": false}')
self.assertEqual(self.dumps(
{2: 3.0, 4.0: 5, False: 1, 6: True}, sort_keys=True),
'{"false": 1, "2": 3.0, "4.0": 5, "6": true}')
'{"2": 3.0, "4.0": 5, "6": true, "false": 1}')

# Issue 16228: Crash on encoding resized list
def test_encode_mutated(self):
Expand Down
4 changes: 0 additions & 4 deletions Lib/test/test_json/test_speedups.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@ def test(name):
self.assertRaises(ZeroDivisionError, test, 'allow_nan')
self.assertRaises(ZeroDivisionError, test, 'sort_keys')

def test_unsortable_keys(self):
with self.assertRaises(TypeError):
self.json.encoder.JSONEncoder(sort_keys=True).encode({'a': 1, 1: 'a'})

def test_current_indent_level(self):
enc = self.json.encoder.c_make_encoder(
markers=None,
Expand Down
84 changes: 76 additions & 8 deletions Modules/_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -1665,14 +1665,12 @@ encoder_listencode_obj(PyEncoderObject *s, PyUnicodeWriter *writer,
}
}

/* Convert the key to the string used in the output. Return 1 and set
*pkeystr on success, 0 if the key is skipped, and -1 on error. */
static int
encoder_encode_key_value(PyEncoderObject *s, PyUnicodeWriter *writer, bool *first,
PyObject *dct, PyObject *key, PyObject *value,
Py_ssize_t indent_level, PyObject *indent_cache,
PyObject *item_separator)
encoder_coerce_key(PyEncoderObject *s, PyObject *key, PyObject **pkeystr)
{
PyObject *keystr = NULL;
int rv;
PyObject *keystr;

if (PyUnicode_Check(key)) {
keystr = Py_NewRef(key);
Expand Down Expand Up @@ -1701,6 +1699,67 @@ encoder_encode_key_value(PyEncoderObject *s, PyUnicodeWriter *writer, bool *firs
if (keystr == NULL) {
return -1;
}
*pkeystr = keystr;
return 1;
}

/* Return a new list of (key string, value) pairs, with the keys converted
and the skipped keys omitted. */
static PyObject *
encoder_coerce_items(PyEncoderObject *s, PyObject *items)
{
PyObject *coerced = PyList_New(0);
if (coerced == NULL) {
return NULL;
}
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); i++) {
PyObject *item = Py_NewRef(PyList_GET_ITEM(items, i));
if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2) {
PyErr_SetString(PyExc_ValueError, "items must return 2-tuples");
Py_DECREF(item);
goto bail;
}
PyObject *keystr;
int res = encoder_coerce_key(s, PyTuple_GET_ITEM(item, 0), &keystr);
if (res <= 0) {
Py_DECREF(item);
if (res < 0) {
goto bail;
}
continue; /* the key is skipped */
}
PyObject *pair = PyTuple_Pack(2, keystr, PyTuple_GET_ITEM(item, 1));
Py_DECREF(keystr);
Py_DECREF(item);
if (pair == NULL) {
goto bail;
}
int rv = PyList_Append(coerced, pair);
Py_DECREF(pair);
if (rv < 0) {
goto bail;
}
}
return coerced;

bail:
Py_DECREF(coerced);
return NULL;
}

static int
encoder_encode_key_value(PyEncoderObject *s, PyUnicodeWriter *writer, bool *first,
PyObject *dct, PyObject *key, PyObject *value,
Py_ssize_t indent_level, PyObject *indent_cache,
PyObject *item_separator)
{
PyObject *keystr;
int rv;

rv = encoder_coerce_key(s, key, &keystr);
if (rv <= 0) {
return rv;
}

if (*first) {
*first = false;
Expand Down Expand Up @@ -1837,10 +1896,19 @@ encoder_listencode_dict(PyEncoderObject *s, PyUnicodeWriter *writer,

if (s->sort_keys || !PyAnyDict_CheckExact(dct)) {
PyObject *items = PyMapping_Items(dct);
if (items == NULL || (s->sort_keys && PyList_Sort(items) < 0)) {
Py_XDECREF(items);
if (items == NULL) {
goto bail;
}
if (s->sort_keys) {
/* Keys are converted to strings before sorting, so that
a dict with mixed key types can be sorted, and the keys
which are skipped do not participate in the sorting. */
Py_SETREF(items, encoder_coerce_items(s, items));
if (items == NULL || PyList_Sort(items) < 0) {
Py_XDECREF(items);
goto bail;
}
}

int result;
Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(items);
Expand Down
Loading