Skip to content

gh-146440: Add "array_hook" parameter to JSON decoders#146441

Open
jsbueno wants to merge 13 commits intopython:mainfrom
jsbueno:feature-json-array-hook
Open

gh-146440: Add "array_hook" parameter to JSON decoders#146441
jsbueno wants to merge 13 commits intopython:mainfrom
jsbueno:feature-json-array-hook

Conversation

@jsbueno
Copy link

@jsbueno jsbueno commented Mar 26, 2026

Add array_hook parameter to json.load and json.loads

gh-146440: Add "array_hook" parameter to JSON decoder 

📚 Documentation preview 📚: https://cpython-previews--146441.org.readthedocs.build/

jsbueno added 2 commits March 25, 2026 22:55
     Add array_hook parameter, to both
     Python and C implementations
@bedevere-app
Copy link

bedevere-app bot commented Mar 26, 2026

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@jsbueno jsbueno changed the title Add json_array parameter to json.load and json.loads gh-146440: Add "array_hook" parameter to JSON decoders Mar 26, 2026
Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is simple enough, and I have no strong objections, although I do not see an immerse need of this feature.

Please add a What's New entry.



_default_decoder = JSONDecoder(object_hook=None, object_pairs_hook=None)
_default_decoder = JSONDecoder(object_hook=None, object_pairs_hook=None, array_hook=None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed. Actually, passing object_hook and object_pairs_hook is also not needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the ther default parameters, then.

@jsbueno jsbueno requested a review from AA-Turner as a code owner March 26, 2026 18:26
def loads(s, *, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
parse_int=None, parse_constant=None, object_pairs_hook=None,
array_hook=None, **kw):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you take this opportunity to fix the indentation? Add 2 spaces.

raise JSONDecodeError("Illegal trailing comma before end of array", s, comma_idx)

if array_hook is not None:
return array_hook(values), end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to have a single return statement, so reuse your code from below:

Suggested change
return array_hook(values), end
values = array_hook(values)

Comment on lines +311 to +316
``array_hook`` is an optional function that will be called with the result
of any literal array decode (a ``list``). The return value of this function will
be used instead of the ``list``. This feature can be used along
``object_pairs_hook`` to customize the resulting data structure - for example,
by setting that to ``frozendict`` and ``array_hook`` to ``tuple``, one can get
a deep immutable data structute from any JSON data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reformatted to fit into 80 columns:

Suggested change
``array_hook`` is an optional function that will be called with the result
of any literal array decode (a ``list``). The return value of this function will
be used instead of the ``list``. This feature can be used along
``object_pairs_hook`` to customize the resulting data structure - for example,
by setting that to ``frozendict`` and ``array_hook`` to ``tuple``, one can get
a deep immutable data structute from any JSON data.
``array_hook`` is an optional function that will be called with the
result of any literal array decode (a ``list``). The return value of
this function will be used instead of the ``list``. This feature can
be used along ``object_pairs_hook`` to customize the resulting data
structure - for example, by setting that to ``frozendict`` and
``array_hook`` to ``tuple``, one can get a deep immutable data
structute from any JSON data.

@@ -0,0 +1,6 @@
:mod:`json`: Add the *array_hook* parameter to :func:`~json.load` and
:func:`~json.loads` functions:
Allow a callback for JSON literal array types to customize Python lists in the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Allow a callback for JSON literal array types to customize Python lists in the
allow a callback for JSON literal array types to customize Python lists in the

goto bail;
}
*next_idx_ptr = idx + 1;
/* if array_hook is not None: rval = array_hook(rval) */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your pseudo-code doesn't describe correctly the code below:

Suggested change
/* if array_hook is not None: rval = array_hook(rval) */
/* if array_hook is not None: return array_hook(rval) */

}
*next_idx_ptr = idx + 1;
/* if array_hook is not None: rval = array_hook(rval) */
if (s->array_hook != Py_None) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (s->array_hook != Py_None) {
if (!Py_IsNone(s->array_hook)) {

Comment on lines +1273 to +1274
if (s->array_hook == NULL)
goto bail;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New code should follow PEP 7:

Suggested change
if (s->array_hook == NULL)
goto bail;
if (s->array_hook == NULL) {
goto bail;
}

Comment on lines +72 to +85
def test_array_hook(self):
s = '[1, 2, 3]'

t = self.loads(s, array_hook=tuple)
self.assertEqual(t, (1, 2, 3))
self.assertEqual(type(t), tuple)
# Array in inner structure
s = '{"xkd": [1, 2, 3]}'
p = {"xkd": (1, 2, 3)}
data = self.loads(s, array_hook=tuple)
self.assertEqual(data, p)
self.assertEqual(type(data["xkd"]), tuple)

self.assertEqual(self.loads('[]', array_hook=tuple), ())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new documentation suggests object_hook=frozendict with array_hook=tuple, so IMO it's is a good opportunity to test them. I suggest a more elaborated test also with nested lists:

Suggested change
def test_array_hook(self):
s = '[1, 2, 3]'
t = self.loads(s, array_hook=tuple)
self.assertEqual(t, (1, 2, 3))
self.assertEqual(type(t), tuple)
# Array in inner structure
s = '{"xkd": [1, 2, 3]}'
p = {"xkd": (1, 2, 3)}
data = self.loads(s, array_hook=tuple)
self.assertEqual(data, p)
self.assertEqual(type(data["xkd"]), tuple)
self.assertEqual(self.loads('[]', array_hook=tuple), ())
def test_array_hook(self):
s = '[1, 2, 3]'
t = self.loads(s, array_hook=tuple)
self.assertEqual(t, (1, 2, 3))
self.assertEqual(type(t), tuple)
# Nested array in inner structure with object_hook
s = '{"xkd": [[1], [2], [3]]}'
p = frozendict(xkd=((1,), (2,), (3,)))
data = self.loads(s, object_hook=frozendict, array_hook=tuple)
self.assertEqual(data, p)
self.assertEqual(type(data), frozendict)
self.assertEqual(type(data["xkd"]), tuple)
for item in data["xkd"]:
self.assertEqual(type(item), tuple)
self.assertEqual(self.loads('[]', array_hook=tuple), ())

of service attacks.

.. function:: loads(s, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
.. versionchanged:: 3.15
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.. versionchanged:: 3.15
.. versionchanged:: next

Co-authored-by: Victor Stinner <vstinner@python.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants