# Add \`array\_hook\` to Json-decoder options

**URL:** <https://discuss.python.org/t/add-array-hook-to-json-decoder-options/106304>\
**Category:** Ideas\
**Created:** [February 27, 2026, 2:54pm UTC](https://discuss.python.org/t/add-array-hook-to-json-decoder-options/106304 "2026-02-27T14:54:52Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![jsbueno](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jsbueno/32/1210_2.png) [@jsbueno](https://discuss.python.org/u/jsbueno)\
**Post date:** [February 27, 2026, 2:54pm UTC](https://discuss.python.org/t/add-array-hook-to-json-decoder-options/106304/1 "2026-02-27T14:54:52Z")

</div>

For something that could be very straightforward:

When decoding a JSON string, the stdlib decoder allows for a series of customizations in the form of callbacks for (almost) every element encountered: `object_hook` for dicts, and `parse_float`, `parse_int` and `parse_constant` for converting scalar values.

The one JSON data type missing in the customization are JSON arrays, which then must be parsed as Python lists. Maybe at the time of writting the API, people simply couldn’t find a use case for that.

But with the advent of Frozendict, if JSON arrays can be decoded as tuples, it is trivial to parse a whole deeply nested data structure to an imutable datastructure, Python side.

The use cases for that would be the same that exists for “frozendict”, so I believe I won’t need to justify then. And JSON decoding would fix this strange asymmetry.

For the record, parsing JSON as a frozen datastructure, given frozendicts, is _almost_ possible with frozendicts and the `object_hook` argument:

```python
frozen = json.loads(
    json_str, object_hook=lambda dct: frozendict({k: (tuple(v) if isinstance(v, list) else v) for k, v in dct.items()}))

```

But this won’t work if the outermost JSON element is itself an array (list) instead of an object (dict).

In addition, or alternatively to an `array_hook` parameter, the decoder could more simply have a `frozen` parameter flag that would decode straight to tuples and frozendicts (and that would be more optimal) .

Also, note that the JSON encoder can handle constant data structures made with frozendicts and tuples by default - so having the option to decode to imutable would also yield a nice symmetry.

* * *

For the record, with the proposed `array_hook` and no special parameter to load the structure as immutable, this is the call that would do it:

```python
frozen = json.loads(
    json_str, object_hook=lambda dct: frozendict(dct), array_hook=lambda lst: tuple(lst))

```

---

<div class="post-metadata">

**Author:** ![jsbueno](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jsbueno/32/1210_2.png) [@jsbueno](https://discuss.python.org/u/jsbueno)\
**Post date:** [March 26, 2026, 2:11am UTC](https://discuss.python.org/t/add-array-hook-to-json-decoder-options/106304/2 "2026-03-26T02:11:56Z")

</div>

As this looks simple enough, and I had no voiced objections, I just went ahead and created a PR here:

> <https://github.com/python/cpython/issues/146440>
>
> \# Feature or enhancement
> 
> \### Proposal:
> 
> The \`json.load\` and \`.loads\` in Python …are customizable, allowing callbacks to be passed for transforming decoded JSON objects, key/value pairs, integers, floats, json constants - and notably absent is a callback to transform JSON arrays before the final Python object is returned.
> 
> With the introduction of \`frozendict\`, a combination of passing \`frozendict\` to either \`object\_hook\` or \`object\_pairs\_hook\`, combined with passing \`tuple\` as an \`array\_hook\` callback, is a straightforward way to get an arbitrarily nested imutable data structure. 
> 
> The advantages of having a structure composed of mappings and sequences, but immutable will be the same that we get for having frozendicts in the first place. In particular, they provide data-structures which can make certain concurrent scenarios in free threading, or when sharing data across sub-interpreters easier to handle.
> 
> And, usefulness apart, this certainly addresses an asymmetry in the API, fixing the fact that from all possible JSON literals, just arrays are not directly customizable when decoding.
> 
> For sake of completeness, it is possible to replace the resulting lists from decoded JSON arrays in both \`object\_hook\` and \`object\_pairs\_hook\` callbacks, but (1) that requires writing a full function with a for-loop looking for any lists, instead of, say, just passing a compatible Sequence class, and (2) if the JSON array happens to be the outermost container in the JSON string, this approach won't convert it, requiring some extra-checking in the caller code, after calling \`json.load\`.
> 
> Current approach:
> \`\`\`
> frozen = json.loads(
> json\_str, object\_hook=lambda dct: frozendict({k: (tuple(v) if isinstance(v, list) else v) for k, v in dct.items()}))
> 
> if isinstance(frozen, list):
> frozen = tuple(frozen)
> \`\`\`
> 
> With the proposed array\_hook parameter:
> \`\`\`
> frozen = json.loads(json\_str, object\_hook=frozendict, array\_hook=tuple)
> \`\`\`
> 
> \### Has this already been discussed elsewhere?
> 
> This is a minor feature, which does not need previous discussion elsewhere
> 
> \### Links to previous discussion of this feature:
> 
> Although it is a a minor feature, I've created the Discourse thread for it, where it raised no eyebrows - seemingly, there are no downsides:
> 
> https://discuss.python.org/t/add-array-hook-to-json-decoder-options/106304
> 
> 
> \### Linked PRs
> \* gh-146441

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [March 26, 2026, 8:33am UTC](https://discuss.python.org/t/add-array-hook-to-json-decoder-options/106304/3 "2026-03-26T08:33:47Z")

</div>

`object_hook` was added in [support a decoding object\_hook · simplejson/simplejson@3035dac · GitHub](https://github.com/simplejson/simplejson/commit/3035dacc8376310196b71012d80a48904342eff1) to support creation of objects depending on the content of the dict. For example:

```python
    >>> import simplejson
    >>> def as_complex(dct):
    ... if ' __complex__' in dct:
    ... return complex(dct['real'], dct['imag'])
    ... return dct
    ... 
    >>> simplejson.loads('{" __complex__": true, "real": 1, "imag": 2}',
    ... object_hook=as_complex)
    (1+2j)

```

This is not the case for JSON arrays. So, `array_hook` would have less use cases than `object_hook`. In any case we can postprocess the result of deseralization, and use not only the object/array content, but also their position in the tree. But this is true also for the original `object_hook` parameter. There are third-party packages to convert JSON to structural tree of objects basing on rules and patterns, they don’t use `object_hook`.

cc @bob

---

<div class="post-metadata">

**Author:** ![bob](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bob/32/33_2.png) [@bob](https://discuss.python.org/u/bob)\
**Post date:** [March 26, 2026, 1:05pm UTC](https://discuss.python.org/t/add-array-hook-to-json-decoder-options/106304/4 "2026-03-26T13:05:35Z")

</div>

I don’t have a strong opinion about it, but the use case for a frozen result sounds reasonable enough to consider.
