Repository: python-telegram-bot/python-telegram-bot

Issue:
[FEATURE] Maintain ujson support.
### What kind of feature are you missing? Where do you notice a shortcoming of PTB?

On a bot that is used enough, the workload of encoding and decoding json when using the telegram API may be a relevant factor to bot performance.

### Describe the solution you'd like

Maintaining support for ujson as an optional dependency to accelerate json decoding and encoding.

### Describe alternatives you've considered

_No response_

### Additional context

I am making this based on https://github.com/python-telegram-bot/python-telegram-bot/pull/3037#issuecomment-1132516188


Discussion:
It's faster than the traditional json of python as i noticed in some of the operations, i did used ujson all over the repo and if it's available in the machine why shouldn't we use it.
why not use orjson? it should be even faster afaik 

Have a look at the [repo](https://github.com/ijl/orjson)
> why not use orjson? it should be even faster afaik
> 
> Have a look at the [repo](https://github.com/ijl/orjson)

That is addressed in https://github.com/python-telegram-bot/python-telegram-bot/pull/3037#issue-1237599033 @WisdomPill 

Resolving patch:
diff --git a/telegram/request/_baserequest.py b/telegram/request/_baserequest.py
--- a/telegram/request/_baserequest.py
+++ b/telegram/request/_baserequest.py
@@ -68,6 +68,11 @@ class BaseRequest(
         finally:
             await request_object.shutdown()
 
+    Tip:
+        JSON encoding and decoding is done with the standard library's :mod:`json` by default.
+        To use a custom library for this, you can override :meth:`parse_json_payload` and implement
+        custom logic to encode the keys of :attr:`telegram.request.RequestData.parameters`.
+
     .. versionadded:: 20.0
     """
 
@@ -163,7 +168,7 @@ async def post(
             connect_timeout=connect_timeout,
             pool_timeout=pool_timeout,
         )
-        json_data = self._parse_json_response(result)
+        json_data = self.parse_json_payload(result)
         # For successful requests, the results are in the 'result' entry
         # see https://core.telegram.org/bots/api#making-requests
         return json_data["result"]
@@ -285,7 +290,7 @@ async def _request_wrapper(
             # 200-299 range are HTTP success statuses
             return payload
 
-        response_data = self._parse_json_response(payload)
+        response_data = self.parse_json_payload(payload)
 
         description = response_data.get("description")
         if description:
@@ -325,16 +330,24 @@ async def _request_wrapper(
         raise NetworkError(f"{message} ({code})")
 
     @staticmethod
-    def _parse_json_response(json_payload: bytes) -> JSONDict:
-        """Try and parse the JSON returned from Telegram.
+    def parse_json_payload(payload: bytes) -> JSONDict:
+        """Parse the JSON returned from Telegram.
+
+        Tip:
+            By default, this method uses the standard library's :func:`json.loads` and
+            ``errors="replace"`` in :meth:`bytes.decode`.
+            You can override it to customize either of these behaviors.
+
+        Args:
+            payload (:obj:`bytes`): The UTF-8 encoded JSON payload as returned by Telegram.
 
         Returns:
             dict: A JSON parsed as Python dict with results.
 
         Raises:
-            TelegramError: If the data could not be json_loaded
+            TelegramError: If loading the JSON data failed
         """
-        decoded_s = json_payload.decode("utf-8", "replace")
+        decoded_s = payload.decode("utf-8", "replace")
         try:
             return json.loads(decoded_s)
         except ValueError as exc:
diff --git a/telegram/request/_requestdata.py b/telegram/request/_requestdata.py
--- a/telegram/request/_requestdata.py
+++ b/telegram/request/_requestdata.py
@@ -64,6 +64,11 @@ def parameters(self) -> Dict[str, Union[str, int, List, Dict]]:
     def json_parameters(self) -> Dict[str, str]:
         """Gives the parameters as mapping of parameter name to the respective JSON encoded
         value.
+
+        Tip:
+            By default, this property uses the standard library's :func:`json.dumps`.
+            To use a custom library for JSON encoding, you can directly encode the keys of
+            :attr:`parameters` - note that string valued keys should not be JSON encoded.
         """
         return {
             param.name: param.json_value
@@ -96,7 +101,13 @@ def parametrized_url(self, url: str, encode_kwargs: Dict[str, Any] = None) -> st
 
     @property
     def json_payload(self) -> bytes:
-        """The parameters as UTF-8 encoded JSON payload."""
+        """The :attr:`parameters` as UTF-8 encoded JSON payload.
+
+        Tip:
+            By default, this property uses the standard library's :func:`json.dumps`.
+            To use a custom library for JSON encoding, you can directly encode the keys of
+            :attr:`parameters` - note that string valued keys should not be JSON encoded.
+        """
         return json.dumps(self.json_parameters).encode("utf-8")
 
     @property


Test patch:
diff --git a/tests/test_request.py b/tests/test_request.py
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -129,6 +129,9 @@ async def test_replaced_unprintable_char(self, monkeypatch, httpx_request):
         monkeypatch.setattr(httpx_request, "do_request", mocker_factory(response=server_response))
 
         assert await httpx_request.post(None, None, None) == "test_string�"
+        # Explicitly call `parse_json_payload` here is well so that this public method is covered
+        # not only implicitly.
+        assert httpx_request.parse_json_payload(server_response) == {"result": "test_string�"}
 
     async def test_illegal_json_response(self, monkeypatch, httpx_request: HTTPXRequest):
         # for proper JSON it should be `"result":` instead of `result:`