Skip to content
Open
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
20 changes: 20 additions & 0 deletions CHANGES/11741.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Added ``json_deserialize`` parameter to :class:`~aiohttp.ClientSession` to configure JSON deserialization at the session level, matching the existing ``json_serialize`` parameter -- by :user:`jtung`.

This allows users to configure a custom JSON deserializer (e.g., ``orjson.loads``) once at session creation, and all calls to :meth:`~aiohttp.ClientResponse.json` will use it by default.

Example usage:

.. code-block:: python

import aiohttp
import orjson

session = aiohttp.ClientSession(
json_serialize=lambda obj: orjson.dumps(obj).decode(),
json_deserialize=orjson.loads
)

async with session.get(url) as response:
data = await response.json() # Uses orjson.loads

Additionally, fixed an inconsistency where ``json_serialize`` parameter defaulted to ``json.dumps`` directly instead of using ``DEFAULT_JSON_ENCODER`` constant, bringing it in line with other parts of the codebase.
21 changes: 18 additions & 3 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import base64
import dataclasses
import hashlib
import json
import os
import sys
import traceback
Expand Down Expand Up @@ -96,7 +95,15 @@
from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse
from .tracing import Trace, TraceConfig
from .typedefs import JSONEncoder, LooseCookies, LooseHeaders, StrOrURL
from .typedefs import (
DEFAULT_JSON_DECODER,
DEFAULT_JSON_ENCODER,
JSONDecoder,
JSONEncoder,
LooseCookies,
LooseHeaders,
StrOrURL,
)

__all__ = (
# client_exceptions
Expand Down Expand Up @@ -234,6 +241,7 @@ class ClientSession:
"_default_auth",
"_version",
"_json_serialize",
"_json_deserialize",
"_requote_redirect_url",
"_timeout",
"_raise_for_status",
Expand Down Expand Up @@ -266,7 +274,8 @@ def __init__(
proxy_auth: BasicAuth | None = None,
skip_auto_headers: Iterable[str] | None = None,
auth: BasicAuth | None = None,
json_serialize: JSONEncoder = json.dumps,
json_serialize: JSONEncoder = DEFAULT_JSON_ENCODER,
json_deserialize: JSONDecoder = DEFAULT_JSON_DECODER,
request_class: type[ClientRequest] = ClientRequest,
response_class: type[ClientResponse] = ClientResponse,
ws_response_class: type[ClientWebSocketResponse] = ClientWebSocketResponse,
Expand Down Expand Up @@ -344,6 +353,7 @@ def __init__(
self._default_auth = auth
self._version = version
self._json_serialize = json_serialize
self._json_deserialize = json_deserialize
self._raise_for_status = raise_for_status
self._auto_decompress = auto_decompress
self._trust_env = trust_env
Expand Down Expand Up @@ -1322,6 +1332,11 @@ def json_serialize(self) -> JSONEncoder:
"""Json serializer callable"""
return self._json_serialize

@property
def json_deserialize(self) -> JSONDecoder:
"""Json deserializer callable"""
return self._json_deserialize

@property
def connector_owner(self) -> bool:
"""Should connector be closed on session closing"""
Expand Down
11 changes: 9 additions & 2 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ class _RequestInfo(NamedTuple):


class RequestInfo(_RequestInfo):

def __new__(
cls,
url: URL,
Expand Down Expand Up @@ -641,7 +640,7 @@ async def json(
self,
*,
encoding: str | None = None,
loads: JSONDecoder = DEFAULT_JSON_DECODER,
loads: JSONDecoder | None = None,
content_type: str | None = "application/json",
) -> Any:
"""Read and decodes JSON response."""
Expand All @@ -663,6 +662,14 @@ async def json(
if encoding is None:
encoding = self.get_encoding()

# Use session's deserializer if loads not explicitly provided
if loads is None:
loads = (
self._session.json_deserialize
if self._session is not None
else DEFAULT_JSON_DECODER
)

return loads(self._body.decode(encoding)) # type: ignore[union-attr]

async def __aenter__(self) -> "ClientResponse":
Expand Down
2 changes: 2 additions & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ cythonized
de
deduplicate
defs
deserialization
deserializer
Dependabot
deprecations
DER
Expand Down
Loading