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

Issue:
Simplify Handling of empty data in `TO.de_json`
We currenty have clauses like

https://github.com/python-telegram-bot/python-telegram-bot/blob/2ac52018c29c791aa0438750e5bcdf2da8945bf8/telegram/_telegramobject.py#L394-L395

and 

https://github.com/python-telegram-bot/python-telegram-bot/blob/2ac52018c29c791aa0438750e5bcdf2da8945bf8/telegram/_telegramobject.py#L462-L463

These sometimes make difficulties for classes that have no required arguments such that `{}` is valid input for `de_json`. Moreover, classes with subclasses (`BotCommandScope` & sim), where `de_json` calls the `de_json` method of a subclass, need to take care of this as well.

AFAIK the main use case for the emtyp-data handling is calling other classes `de_json`. E.g. `Update.de_json` calls

```python
data["message"] = Message.de_json(data.get("message"), bot)
```

and `data.get("message")` might be `None`.

Neither `TO.de_json` nor `TO.de_list` documents how empty data (`{}`) or `None` is handled. Note that the type hints do give something away, but they are explicitly excluded from our stability policy.

To simplify internal logic, I propose to

* switch to uniform `if data is None: return None`
* optionally even remove that and think of a clean way to handle the `Message.de_json(data.get("message"), bot)` case


Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index 470d2a2aac1..9e944f66958 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,6 +67,7 @@ docs/_build/
 # PyBuilder
 target/
 .idea/
+.run/
 
 # Sublime Text 2
 *.sublime*
diff --git a/telegram/_bot.py b/telegram/_bot.py
index 8cb2431a336..a47e7c2137c 100644
--- a/telegram/_bot.py
+++ b/telegram/_bot.py
@@ -901,7 +901,7 @@ async def get_me(
             api_kwargs=api_kwargs,
         )
         self._bot_user = User.de_json(result, self)
-        return self._bot_user  # type: ignore[return-value]
+        return self._bot_user
 
     async def send_message(
         self,
@@ -3689,7 +3689,7 @@ async def save_prepared_inline_message(
             "allow_group_chats": allow_group_chats,
             "allow_channel_chats": allow_channel_chats,
         }
-        return PreparedInlineMessage.de_json(  # type: ignore[return-value]
+        return PreparedInlineMessage.de_json(
             await self._post(
                 "savePreparedInlineMessage",
                 data,
@@ -3744,7 +3744,7 @@ async def get_user_profile_photos(
             api_kwargs=api_kwargs,
         )
 
-        return UserProfilePhotos.de_json(result, self)  # type: ignore[return-value]
+        return UserProfilePhotos.de_json(result, self)
 
     async def get_file(
         self,
@@ -3809,7 +3809,7 @@ async def get_file(
         if file_path and not is_local_file(file_path):
             result["file_path"] = f"{self._base_file_url}/{file_path}"
 
-        return File.de_json(result, self)  # type: ignore[return-value]
+        return File.de_json(result, self)
 
     async def ban_chat_member(
         self,
@@ -4729,7 +4729,7 @@ async def get_chat(
             api_kwargs=api_kwargs,
         )
 
-        return ChatFullInfo.de_json(result, self)  # type: ignore[return-value]
+        return ChatFullInfo.de_json(result, self)
 
     async def get_chat_administrators(
         self,
@@ -4842,7 +4842,7 @@ async def get_chat_member(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
         )
-        return ChatMember.de_json(result, self)  # type: ignore[return-value]
+        return ChatMember.de_json(result, self)
 
     async def set_chat_sticker_set(
         self,
@@ -4937,7 +4937,7 @@ async def get_webhook_info(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
         )
-        return WebhookInfo.de_json(result, self)  # type: ignore[return-value]
+        return WebhookInfo.de_json(result, self)
 
     async def set_game_score(
         self,
@@ -5444,7 +5444,7 @@ async def answer_web_app_query(
             api_kwargs=api_kwargs,
         )
 
-        return SentWebAppMessage.de_json(api_result, self)  # type: ignore[return-value]
+        return SentWebAppMessage.de_json(api_result, self)
 
     async def restrict_chat_member(
         self,
@@ -5858,7 +5858,7 @@ async def create_chat_invite_link(
             api_kwargs=api_kwargs,
         )
 
-        return ChatInviteLink.de_json(result, self)  # type: ignore[return-value]
+        return ChatInviteLink.de_json(result, self)
 
     async def edit_chat_invite_link(
         self,
@@ -5937,7 +5937,7 @@ async def edit_chat_invite_link(
             api_kwargs=api_kwargs,
         )
 
-        return ChatInviteLink.de_json(result, self)  # type: ignore[return-value]
+        return ChatInviteLink.de_json(result, self)
 
     async def revoke_chat_invite_link(
         self,
@@ -5984,7 +5984,7 @@ async def revoke_chat_invite_link(
             api_kwargs=api_kwargs,
         )
 
-        return ChatInviteLink.de_json(result, self)  # type: ignore[return-value]
+        return ChatInviteLink.de_json(result, self)
 
     async def approve_chat_join_request(
         self,
@@ -6456,7 +6456,7 @@ async def get_sticker_set(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
         )
-        return StickerSet.de_json(result, self)  # type: ignore[return-value]
+        return StickerSet.de_json(result, self)
 
     async def get_custom_emoji_stickers(
         self,
@@ -6559,7 +6559,7 @@ async def upload_sticker_file(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
         )
-        return File.de_json(result, self)  # type: ignore[return-value]
+        return File.de_json(result, self)
 
     async def add_sticker_to_set(
         self,
@@ -7416,7 +7416,7 @@ async def stop_poll(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
         )
-        return Poll.de_json(result, self)  # type: ignore[return-value]
+        return Poll.de_json(result, self)
 
     async def send_dice(
         self,
@@ -7572,7 +7572,7 @@ async def get_my_default_administrator_rights(
             api_kwargs=api_kwargs,
         )
 
-        return ChatAdministratorRights.de_json(result, self)  # type: ignore[return-value]
+        return ChatAdministratorRights.de_json(result, self)
 
     async def set_my_default_administrator_rights(
         self,
@@ -7982,7 +7982,7 @@ async def copy_message(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
         )
-        return MessageId.de_json(result, self)  # type: ignore[return-value]
+        return MessageId.de_json(result, self)
 
     async def copy_messages(
         self,
@@ -8133,7 +8133,7 @@ async def get_chat_menu_button(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
         )
-        return MenuButton.de_json(result, bot=self)  # type: ignore[return-value]
+        return MenuButton.de_json(result, bot=self)
 
     async def create_invoice_link(
         self,
@@ -8384,7 +8384,7 @@ async def create_forum_topic(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
         )
-        return ForumTopic.de_json(result, self)  # type: ignore[return-value]
+        return ForumTopic.de_json(result, self)
 
     async def edit_forum_topic(
         self,
@@ -8972,7 +8972,7 @@ async def get_my_description(
 
         """
         data = {"language_code": language_code}
-        return BotDescription.de_json(  # type: ignore[return-value]
+        return BotDescription.de_json(
             await self._post(
                 "getMyDescription",
                 data,
@@ -9011,7 +9011,7 @@ async def get_my_short_description(
 
         """
         data = {"language_code": language_code}
-        return BotShortDescription.de_json(  # type: ignore[return-value]
+        return BotShortDescription.de_json(
             await self._post(
                 "getMyShortDescription",
                 data,
@@ -9097,7 +9097,7 @@ async def get_my_name(
 
         """
         data = {"language_code": language_code}
-        return BotName.de_json(  # type: ignore[return-value]
+        return BotName.de_json(
             await self._post(
                 "getMyName",
                 data,
@@ -9139,7 +9139,7 @@ async def get_user_chat_boosts(
             :class:`telegram.error.TelegramError`
         """
         data: JSONDict = {"chat_id": chat_id, "user_id": user_id}
-        return UserChatBoosts.de_json(  # type: ignore[return-value]
+        return UserChatBoosts.de_json(
             await self._post(
                 "getUserChatBoosts",
                 data,
@@ -9263,7 +9263,7 @@ async def get_business_connection(
             :class:`telegram.error.TelegramError`
         """
         data: JSONDict = {"business_connection_id": business_connection_id}
-        return BusinessConnection.de_json(  # type: ignore[return-value]
+        return BusinessConnection.de_json(
             await self._post(
                 "getBusinessConnection",
                 data,
@@ -9402,7 +9402,7 @@ async def get_star_transactions(
 
         data: JSONDict = {"offset": offset, "limit": limit}
 
-        return StarTransactions.de_json(  # type: ignore[return-value]
+        return StarTransactions.de_json(
             await self._post(
                 "getStarTransactions",
                 data,
@@ -9628,7 +9628,7 @@ async def create_chat_subscription_invite_link(
             api_kwargs=api_kwargs,
         )
 
-        return ChatInviteLink.de_json(result, self)  # type: ignore[return-value]
+        return ChatInviteLink.de_json(result, self)
 
     async def edit_chat_subscription_invite_link(
         self,
@@ -9681,7 +9681,7 @@ async def edit_chat_subscription_invite_link(
             api_kwargs=api_kwargs,
         )
 
-        return ChatInviteLink.de_json(result, self)  # type: ignore[return-value]
+        return ChatInviteLink.de_json(result, self)
 
     async def get_available_gifts(
         self,
@@ -9703,7 +9703,7 @@ async def get_available_gifts(
         Raises:
             :class:`telegram.error.TelegramError`
         """
-        return Gifts.de_json(  # type: ignore[return-value]
+        return Gifts.de_json(
             await self._post(
                 "getAvailableGifts",
                 read_timeout=read_timeout,
diff --git a/telegram/_botcommandscope.py b/telegram/_botcommandscope.py
index 0c339a3da15..dbce54c32c4 100644
--- a/telegram/_botcommandscope.py
+++ b/telegram/_botcommandscope.py
@@ -84,9 +84,7 @@ def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None):
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["BotCommandScope"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BotCommandScope":
         """Converts JSON data to the appropriate :class:`BotCommandScope` object, i.e. takes
         care of selecting the correct subclass.
 
@@ -104,9 +102,6 @@ def de_json(
         """
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         _class_mapping: dict[str, type[BotCommandScope]] = {
             cls.DEFAULT: BotCommandScopeDefault,
             cls.ALL_PRIVATE_CHATS: BotCommandScopeAllPrivateChats,
diff --git a/telegram/_business.py b/telegram/_business.py
index 412eae73051..95607c24344 100644
--- a/telegram/_business.py
+++ b/telegram/_business.py
@@ -27,7 +27,7 @@
 from telegram._files.sticker import Sticker
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -106,20 +106,15 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["BusinessConnection"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessConnection":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
         data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
-        data["user"] = User.de_json(data.get("user"), bot)
+        data["user"] = de_json_optional(data.get("user"), User, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -177,16 +172,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["BusinessMessagesDeleted"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessMessagesDeleted":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -236,16 +226,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["BusinessIntro"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessIntro":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
+        data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -290,16 +275,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["BusinessLocation"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessLocation":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["location"] = Location.de_json(data.get("location"), bot)
+        data["location"] = de_json_optional(data.get("location"), Location, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -439,17 +419,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["BusinessOpeningHours"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessOpeningHours":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["opening_hours"] = BusinessOpeningHoursInterval.de_list(
-            data.get("opening_hours"), bot
+        data["opening_hours"] = de_list_optional(
+            data.get("opening_hours"), BusinessOpeningHoursInterval, bot
         )
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_callbackquery.py b/telegram/_callbackquery.py
index 8febcca0387..83efbfaa675 100644
--- a/telegram/_callbackquery.py
+++ b/telegram/_callbackquery.py
@@ -26,6 +26,7 @@
 from telegram._message import MaybeInaccessibleMessage, Message
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.defaultvalue import DEFAULT_NONE
 from telegram._utils.types import JSONDict, ODVInput, ReplyMarkup
 
@@ -149,17 +150,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["CallbackQuery"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "CallbackQuery":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["from_user"] = User.de_json(data.pop("from", None), bot)
-        data["message"] = Message.de_json(data.get("message"), bot)
+        data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
+        data["message"] = de_json_optional(data.get("message"), Message, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_chatbackground.py b/telegram/_chatbackground.py
index 4d41e55ef64..37fc0c81ca6 100644
--- a/telegram/_chatbackground.py
+++ b/telegram/_chatbackground.py
@@ -24,7 +24,7 @@
 from telegram._files.document import Document
 from telegram._telegramobject import TelegramObject
 from telegram._utils import enum
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, parse_sequence_arg
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -79,15 +79,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["BackgroundFill"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BackgroundFill":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         _class_mapping: dict[str, type[BackgroundFill]] = {
             cls.SOLID: BackgroundFillSolid,
             cls.GRADIENT: BackgroundFillGradient,
@@ -270,15 +265,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["BackgroundType"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BackgroundType":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         _class_mapping: dict[str, type[BackgroundType]] = {
             cls.FILL: BackgroundTypeFill,
             cls.WALLPAPER: BackgroundTypeWallpaper,
@@ -290,10 +280,10 @@ def de_json(
             return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
 
         if "fill" in data:
-            data["fill"] = BackgroundFill.de_json(data.get("fill"), bot)
+            data["fill"] = de_json_optional(data.get("fill"), BackgroundFill, bot)
 
         if "document" in data:
-            data["document"] = Document.de_json(data.get("document"), bot)
+            data["document"] = de_json_optional(data.get("document"), Document, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -533,15 +523,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatBackground"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBackground":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["type"] = BackgroundType.de_json(data.get("type"), bot)
+        data["type"] = de_json_optional(data.get("type"), BackgroundType, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_chatboost.py b/telegram/_chatboost.py
index 6f45a275677..678b713afc3 100644
--- a/telegram/_chatboost.py
+++ b/telegram/_chatboost.py
@@ -26,7 +26,7 @@
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
 from telegram._utils import enum
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -110,15 +110,10 @@ def __init__(self, source: str, *, api_kwargs: Optional[JSONDict] = None):
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatBoostSource"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBoostSource":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         _class_mapping: dict[str, type[ChatBoostSource]] = {
             cls.PREMIUM: ChatBoostSourcePremium,
             cls.GIFT_CODE: ChatBoostSourceGiftCode,
@@ -129,7 +124,7 @@ def de_json(
             return _class_mapping[data.pop("source")].de_json(data=data, bot=bot)
 
         if "user" in data:
-            data["user"] = User.de_json(data.get("user"), bot)
+            data["user"] = de_json_optional(data.get("user"), User, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -290,19 +285,14 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatBoost"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBoost":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["source"] = ChatBoostSource.de_json(data.get("source"), bot)
+        data["source"] = de_json_optional(data.get("source"), ChatBoostSource, bot)
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
-        data["add_date"] = from_timestamp(data["add_date"], tzinfo=loc_tzinfo)
-        data["expiration_date"] = from_timestamp(data["expiration_date"], tzinfo=loc_tzinfo)
+        data["add_date"] = from_timestamp(data.get("add_date"), tzinfo=loc_tzinfo)
+        data["expiration_date"] = from_timestamp(data.get("expiration_date"), tzinfo=loc_tzinfo)
 
         return super().de_json(data=data, bot=bot)
 
@@ -342,17 +332,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatBoostUpdated"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBoostUpdated":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
-        data["boost"] = ChatBoost.de_json(data.get("boost"), bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
+        data["boost"] = de_json_optional(data.get("boost"), ChatBoost, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -401,19 +386,14 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatBoostRemoved"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBoostRemoved":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
-        data["source"] = ChatBoostSource.de_json(data.get("source"), bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
+        data["source"] = de_json_optional(data.get("source"), ChatBoostSource, bot)
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
-        data["remove_date"] = from_timestamp(data["remove_date"], tzinfo=loc_tzinfo)
+        data["remove_date"] = from_timestamp(data.get("remove_date"), tzinfo=loc_tzinfo)
 
         return super().de_json(data=data, bot=bot)
 
@@ -450,15 +430,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["UserChatBoosts"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "UserChatBoosts":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["boosts"] = ChatBoost.de_list(data.get("boosts"), bot)
+        data["boosts"] = de_list_optional(data.get("boosts"), ChatBoost, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_chatfullinfo.py b/telegram/_chatfullinfo.py
index ebf8e8e249d..c763efca78e 100644
--- a/telegram/_chatfullinfo.py
+++ b/telegram/_chatfullinfo.py
@@ -28,7 +28,7 @@
 from telegram._chatpermissions import ChatPermissions
 from telegram._files.chatphoto import ChatPhoto
 from telegram._reaction import ReactionType
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -512,15 +512,10 @@ def __init__(
             self.can_send_paid_media: Optional[bool] = can_send_paid_media
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatFullInfo"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatFullInfo":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
@@ -528,7 +523,7 @@ def de_json(
             data.get("emoji_status_expiration_date"), tzinfo=loc_tzinfo
         )
 
-        data["photo"] = ChatPhoto.de_json(data.get("photo"), bot)
+        data["photo"] = de_json_optional(data.get("photo"), ChatPhoto, bot)
 
         from telegram import (  # pylint: disable=import-outside-toplevel
             BusinessIntro,
@@ -537,16 +532,20 @@ def de_json(
             Message,
         )
 
-        data["pinned_message"] = Message.de_json(data.get("pinned_message"), bot)
-        data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot)
-        data["location"] = ChatLocation.de_json(data.get("location"), bot)
-        data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot)
-        data["birthdate"] = Birthdate.de_json(data.get("birthdate"), bot)
-        data["personal_chat"] = Chat.de_json(data.get("personal_chat"), bot)
-        data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot)
-        data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot)
-        data["business_opening_hours"] = BusinessOpeningHours.de_json(
-            data.get("business_opening_hours"), bot
+        data["pinned_message"] = de_json_optional(data.get("pinned_message"), Message, bot)
+        data["permissions"] = de_json_optional(data.get("permissions"), ChatPermissions, bot)
+        data["location"] = de_json_optional(data.get("location"), ChatLocation, bot)
+        data["available_reactions"] = de_list_optional(
+            data.get("available_reactions"), ReactionType, bot
+        )
+        data["birthdate"] = de_json_optional(data.get("birthdate"), Birthdate, bot)
+        data["personal_chat"] = de_json_optional(data.get("personal_chat"), Chat, bot)
+        data["business_intro"] = de_json_optional(data.get("business_intro"), BusinessIntro, bot)
+        data["business_location"] = de_json_optional(
+            data.get("business_location"), BusinessLocation, bot
+        )
+        data["business_opening_hours"] = de_json_optional(
+            data.get("business_opening_hours"), BusinessOpeningHours, bot
         )
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_chatinvitelink.py b/telegram/_chatinvitelink.py
index b8067242087..289ee48bdba 100644
--- a/telegram/_chatinvitelink.py
+++ b/telegram/_chatinvitelink.py
@@ -22,6 +22,7 @@
 
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -177,19 +178,14 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatInviteLink"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatInviteLink":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-        data["creator"] = User.de_json(data.get("creator"), bot)
+        data["creator"] = de_json_optional(data.get("creator"), User, bot)
         data["expire_date"] = from_timestamp(data.get("expire_date", None), tzinfo=loc_tzinfo)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_chatjoinrequest.py b/telegram/_chatjoinrequest.py
index 87305521035..048b6a80b5d 100644
--- a/telegram/_chatjoinrequest.py
+++ b/telegram/_chatjoinrequest.py
@@ -24,6 +24,7 @@
 from telegram._chatinvitelink import ChatInviteLink
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.defaultvalue import DEFAULT_NONE
 from telegram._utils.types import JSONDict, ODVInput
@@ -129,22 +130,17 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatJoinRequest"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatJoinRequest":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
-        data["from_user"] = User.de_json(data.pop("from", None), bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
+        data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
         data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
-        data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
+        data["invite_link"] = de_json_optional(data.get("invite_link"), ChatInviteLink, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_chatlocation.py b/telegram/_chatlocation.py
index 7e227226f1a..4514b2566db 100644
--- a/telegram/_chatlocation.py
+++ b/telegram/_chatlocation.py
@@ -23,6 +23,7 @@
 from telegram import constants
 from telegram._files.location import Location
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -68,16 +69,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatLocation"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatLocation":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["location"] = Location.de_json(data.get("location"), bot)
+        data["location"] = de_json_optional(data.get("location"), Location, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_chatmember.py b/telegram/_chatmember.py
index 85fd2e9304e..d9f256aa2c1 100644
--- a/telegram/_chatmember.py
+++ b/telegram/_chatmember.py
@@ -24,6 +24,7 @@
 from telegram import constants
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -105,15 +106,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatMember"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatMember":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         _class_mapping: dict[str, type[ChatMember]] = {
             cls.OWNER: ChatMemberOwner,
             cls.ADMINISTRATOR: ChatMemberAdministrator,
@@ -126,12 +122,12 @@ def de_json(
         if cls is ChatMember and data.get("status") in _class_mapping:
             return _class_mapping[data.pop("status")].de_json(data=data, bot=bot)
 
-        data["user"] = User.de_json(data.get("user"), bot)
+        data["user"] = de_json_optional(data.get("user"), User, bot)
         if "until_date" in data:
             # Get the local timezone from the bot if it has defaults
             loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-            data["until_date"] = from_timestamp(data["until_date"], tzinfo=loc_tzinfo)
+            data["until_date"] = from_timestamp(data.get("until_date"), tzinfo=loc_tzinfo)
 
         # This is a deprecated field that TG still returns for backwards compatibility
         # Let's filter it out to speed up the de-json process
diff --git a/telegram/_chatmemberupdated.py b/telegram/_chatmemberupdated.py
index ca6822fed3a..5aeab80a1fa 100644
--- a/telegram/_chatmemberupdated.py
+++ b/telegram/_chatmemberupdated.py
@@ -25,6 +25,7 @@
 from telegram._chatmember import ChatMember
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -141,24 +142,19 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatMemberUpdated"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatMemberUpdated":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
-        data["from_user"] = User.de_json(data.pop("from", None), bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
+        data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
         data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
-        data["old_chat_member"] = ChatMember.de_json(data.get("old_chat_member"), bot)
-        data["new_chat_member"] = ChatMember.de_json(data.get("new_chat_member"), bot)
-        data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
+        data["old_chat_member"] = de_json_optional(data.get("old_chat_member"), ChatMember, bot)
+        data["new_chat_member"] = de_json_optional(data.get("new_chat_member"), ChatMember, bot)
+        data["invite_link"] = de_json_optional(data.get("invite_link"), ChatInviteLink, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_chatpermissions.py b/telegram/_chatpermissions.py
index a7b1b1097ab..e70e858f291 100644
--- a/telegram/_chatpermissions.py
+++ b/telegram/_chatpermissions.py
@@ -231,15 +231,10 @@ def no_permissions(cls) -> "ChatPermissions":
         return cls(*(14 * (False,)))
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatPermissions"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatPermissions":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         api_kwargs = {}
         # This is a deprecated field that TG still returns for backwards compatibility
         # Let's filter it out to speed up the de-json process
diff --git a/telegram/_choseninlineresult.py b/telegram/_choseninlineresult.py
index ee17d9376cd..e3754039230 100644
--- a/telegram/_choseninlineresult.py
+++ b/telegram/_choseninlineresult.py
@@ -24,6 +24,7 @@
 from telegram._files.location import Location
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -92,18 +93,13 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChosenInlineResult"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChosenInlineResult":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Required
-        data["from_user"] = User.de_json(data.pop("from", None), bot)
+        data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
         # Optionals
-        data["location"] = Location.de_json(data.get("location"), bot)
+        data["location"] = de_json_optional(data.get("location"), Location, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_files/_basethumbedmedium.py b/telegram/_files/_basethumbedmedium.py
index a3dff3abfe4..2008475c2f2 100644
--- a/telegram/_files/_basethumbedmedium.py
+++ b/telegram/_files/_basethumbedmedium.py
@@ -21,6 +21,7 @@
 
 from telegram._files._basemedium import _BaseMedium
 from telegram._files.photosize import PhotoSize
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -82,17 +83,14 @@ def __init__(
 
     @classmethod
     def de_json(
-        cls: type[ThumbedMT_co], data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional[ThumbedMT_co]:
+        cls: type[ThumbedMT_co], data: JSONDict, bot: Optional["Bot"] = None
+    ) -> ThumbedMT_co:
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # In case this wasn't already done by the subclass
         if not isinstance(data.get("thumbnail"), PhotoSize):
-            data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
+            data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot)
 
         api_kwargs = {}
         # This is a deprecated field that TG still returns for backwards compatibility
diff --git a/telegram/_files/sticker.py b/telegram/_files/sticker.py
index d5e6d8b90c9..0bf63d4b073 100644
--- a/telegram/_files/sticker.py
+++ b/telegram/_files/sticker.py
@@ -26,7 +26,7 @@
 from telegram._files.photosize import PhotoSize
 from telegram._telegramobject import TelegramObject
 from telegram._utils import enum
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -194,16 +194,13 @@ def __init__(
     """:const:`telegram.constants.StickerType.CUSTOM_EMOJI`"""
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Sticker"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Sticker":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
-        data["mask_position"] = MaskPosition.de_json(data.get("mask_position"), bot)
-        data["premium_animation"] = File.de_json(data.get("premium_animation"), bot)
+        data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot)
+        data["mask_position"] = de_json_optional(data.get("mask_position"), MaskPosition, bot)
+        data["premium_animation"] = de_json_optional(data.get("premium_animation"), File, bot)
 
         api_kwargs = {}
         # This is a deprecated field that TG still returns for backwards compatibility
@@ -306,15 +303,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["StickerSet"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "StickerSet":
         """See :meth:`telegram.TelegramObject.de_json`."""
-        if not data:
-            return None
+        data = cls._parse_data(data)
 
-        data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
-        data["stickers"] = Sticker.de_list(data.get("stickers"), bot)
+        data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot)
+        data["stickers"] = de_list_optional(data.get("stickers"), Sticker, bot)
 
         api_kwargs = {}
         # These are deprecated fields that TG still returns for backwards compatibility
diff --git a/telegram/_files/venue.py b/telegram/_files/venue.py
index c169a52b3be..fd9cbdf69f0 100644
--- a/telegram/_files/venue.py
+++ b/telegram/_files/venue.py
@@ -22,6 +22,7 @@
 
 from telegram._files.location import Location
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -103,13 +104,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Venue"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Venue":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["location"] = Location.de_json(data.get("location"), bot)
+        data["location"] = de_json_optional(data.get("location"), Location, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_games/game.py b/telegram/_games/game.py
index ab74276ced0..bd8cf19caea 100644
--- a/telegram/_games/game.py
+++ b/telegram/_games/game.py
@@ -24,7 +24,7 @@
 from telegram._files.photosize import PhotoSize
 from telegram._messageentity import MessageEntity
 from telegram._telegramobject import TelegramObject
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.strings import TextEncoding
 from telegram._utils.types import JSONDict
 
@@ -124,16 +124,13 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Game"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Game":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
-        data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
-        data["animation"] = Animation.de_json(data.get("animation"), bot)
+        data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
+        data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot)
+        data["animation"] = de_json_optional(data.get("animation"), Animation, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_games/gamehighscore.py b/telegram/_games/gamehighscore.py
index ad3dbd7b910..2866b59fb99 100644
--- a/telegram/_games/gamehighscore.py
+++ b/telegram/_games/gamehighscore.py
@@ -22,6 +22,7 @@
 
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -61,15 +62,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["GameHighScore"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "GameHighScore":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["user"] = User.de_json(data.get("user"), bot)
+        data["user"] = de_json_optional(data.get("user"), User, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_gifts.py b/telegram/_gifts.py
index d711320911f..d068923c6df 100644
--- a/telegram/_gifts.py
+++ b/telegram/_gifts.py
@@ -23,7 +23,7 @@
 
 from telegram._files.sticker import Sticker
 from telegram._telegramobject import TelegramObject
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -99,14 +99,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Gift"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Gift":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
+        data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
         return super().de_json(data=data, bot=bot)
 
 
@@ -142,12 +139,9 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Gifts"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Gifts":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["gifts"] = Gift.de_list(data.get("gifts"), bot)
+        data["gifts"] = de_list_optional(data.get("gifts"), Gift, bot)
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_giveaway.py b/telegram/_giveaway.py
index 67c6b235c14..d7d086e6548 100644
--- a/telegram/_giveaway.py
+++ b/telegram/_giveaway.py
@@ -24,7 +24,7 @@
 from telegram._chat import Chat
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -137,19 +137,14 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["Giveaway"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Giveaway":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-        data["chats"] = tuple(Chat.de_list(data.get("chats"), bot))
+        data["chats"] = de_list_optional(data.get("chats"), Chat, bot)
         data["winners_selection_date"] = from_timestamp(
             data.get("winners_selection_date"), tzinfo=loc_tzinfo
         )
@@ -299,20 +294,15 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["GiveawayWinners"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "GiveawayWinners":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
-        data["winners"] = tuple(User.de_list(data.get("winners"), bot))
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
+        data["winners"] = de_list_optional(data.get("winners"), User, bot)
         data["winners_selection_date"] = from_timestamp(
             data.get("winners_selection_date"), tzinfo=loc_tzinfo
         )
@@ -376,18 +366,13 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["GiveawayCompleted"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "GiveawayCompleted":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
         # Unfortunately, this needs to be here due to cyclic imports
         from telegram._message import Message  # pylint: disable=import-outside-toplevel
 
-        data["giveaway_message"] = Message.de_json(data.get("giveaway_message"), bot)
+        data["giveaway_message"] = de_json_optional(data.get("giveaway_message"), Message, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py
index 61169cac38c..07d0eed3b2d 100644
--- a/telegram/_inline/inlinekeyboardbutton.py
+++ b/telegram/_inline/inlinekeyboardbutton.py
@@ -26,6 +26,7 @@
 from telegram._loginurl import LoginUrl
 from telegram._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 from telegram._webappinfo import WebAppInfo
 
@@ -296,22 +297,17 @@ def _set_id_attrs(self) -> None:
         )
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["InlineKeyboardButton"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InlineKeyboardButton":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["login_url"] = LoginUrl.de_json(data.get("login_url"), bot)
-        data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
-        data["callback_game"] = CallbackGame.de_json(data.get("callback_game"), bot)
-        data["switch_inline_query_chosen_chat"] = SwitchInlineQueryChosenChat.de_json(
-            data.get("switch_inline_query_chosen_chat"), bot
+        data["login_url"] = de_json_optional(data.get("login_url"), LoginUrl, bot)
+        data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot)
+        data["callback_game"] = de_json_optional(data.get("callback_game"), CallbackGame, bot)
+        data["switch_inline_query_chosen_chat"] = de_json_optional(
+            data.get("switch_inline_query_chosen_chat"), SwitchInlineQueryChosenChat, bot
         )
-        data["copy_text"] = CopyTextButton.de_json(data.get("copy_text"), bot)
+        data["copy_text"] = de_json_optional(data.get("copy_text"), CopyTextButton, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_inline/inlinekeyboardmarkup.py b/telegram/_inline/inlinekeyboardmarkup.py
index 834225e761e..64fd8b49124 100644
--- a/telegram/_inline/inlinekeyboardmarkup.py
+++ b/telegram/_inline/inlinekeyboardmarkup.py
@@ -91,12 +91,8 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["InlineKeyboardMarkup"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InlineKeyboardMarkup":
         """See :meth:`telegram.TelegramObject.de_json`."""
-        if not data:
-            return None
 
         keyboard = []
         for row in data["inline_keyboard"]:
diff --git a/telegram/_inline/inlinequery.py b/telegram/_inline/inlinequery.py
index dd6b957d0a7..4cf7f5a7366 100644
--- a/telegram/_inline/inlinequery.py
+++ b/telegram/_inline/inlinequery.py
@@ -27,6 +27,7 @@
 from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.defaultvalue import DEFAULT_NONE
 from telegram._utils.types import JSONDict, ODVInput
 
@@ -126,17 +127,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["InlineQuery"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InlineQuery":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["from_user"] = User.de_json(data.pop("from", None), bot)
-        data["location"] = Location.de_json(data.get("location"), bot)
+        data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
+        data["location"] = de_json_optional(data.get("location"), Location, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_inline/inlinequeryresultsbutton.py b/telegram/_inline/inlinequeryresultsbutton.py
index 11ba092c540..513a366df9f 100644
--- a/telegram/_inline/inlinequeryresultsbutton.py
+++ b/telegram/_inline/inlinequeryresultsbutton.py
@@ -22,6 +22,7 @@
 
 from telegram import constants
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 from telegram._webappinfo import WebAppInfo
 
@@ -97,14 +98,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["InlineQueryResultsButton"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InlineQueryResultsButton":
         """See :meth:`telegram.TelegramObject.de_json`."""
-        if not data:
-            return None
 
-        data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
+        data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_inline/inputinvoicemessagecontent.py b/telegram/_inline/inputinvoicemessagecontent.py
index ecef3940a70..11f248513ee 100644
--- a/telegram/_inline/inputinvoicemessagecontent.py
+++ b/telegram/_inline/inputinvoicemessagecontent.py
@@ -22,7 +22,7 @@
 
 from telegram._inline.inputmessagecontent import InputMessageContent
 from telegram._payment.labeledprice import LabeledPrice
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -254,15 +254,10 @@ def __init__(
             )
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["InputInvoiceMessageContent"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InputInvoiceMessageContent":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["prices"] = LabeledPrice.de_list(data.get("prices"), bot)
+        data["prices"] = de_list_optional(data.get("prices"), LabeledPrice, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_inline/preparedinlinemessage.py b/telegram/_inline/preparedinlinemessage.py
index 35f4628ea88..ec2f49b5660 100644
--- a/telegram/_inline/preparedinlinemessage.py
+++ b/telegram/_inline/preparedinlinemessage.py
@@ -67,15 +67,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PreparedInlineMessage"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PreparedInlineMessage":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
         data["expiration_date"] = from_timestamp(data.get("expiration_date"), tzinfo=loc_tzinfo)
diff --git a/telegram/_keyboardbutton.py b/telegram/_keyboardbutton.py
index 7bb8949a459..8fd29846946 100644
--- a/telegram/_keyboardbutton.py
+++ b/telegram/_keyboardbutton.py
@@ -23,6 +23,7 @@
 from telegram._keyboardbuttonpolltype import KeyboardButtonPollType
 from telegram._keyboardbuttonrequest import KeyboardButtonRequestChat, KeyboardButtonRequestUsers
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 from telegram._webappinfo import WebAppInfo
 
@@ -168,19 +169,20 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["KeyboardButton"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "KeyboardButton":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["request_poll"] = KeyboardButtonPollType.de_json(data.get("request_poll"), bot)
-        data["request_users"] = KeyboardButtonRequestUsers.de_json(data.get("request_users"), bot)
-        data["request_chat"] = KeyboardButtonRequestChat.de_json(data.get("request_chat"), bot)
-        data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
+        data["request_poll"] = de_json_optional(
+            data.get("request_poll"), KeyboardButtonPollType, bot
+        )
+        data["request_users"] = de_json_optional(
+            data.get("request_users"), KeyboardButtonRequestUsers, bot
+        )
+        data["request_chat"] = de_json_optional(
+            data.get("request_chat"), KeyboardButtonRequestChat, bot
+        )
+        data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot)
 
         api_kwargs = {}
         # This is a deprecated field that TG still returns for backwards compatibility
diff --git a/telegram/_keyboardbuttonrequest.py b/telegram/_keyboardbuttonrequest.py
index b929f0b00d2..620e6e16911 100644
--- a/telegram/_keyboardbuttonrequest.py
+++ b/telegram/_keyboardbuttonrequest.py
@@ -22,6 +22,7 @@
 
 from telegram._chatadministratorrights import ChatAdministratorRights
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -257,20 +258,15 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["KeyboardButtonRequestChat"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "KeyboardButtonRequestChat":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["user_administrator_rights"] = ChatAdministratorRights.de_json(
-            data.get("user_administrator_rights"), bot
+        data["user_administrator_rights"] = de_json_optional(
+            data.get("user_administrator_rights"), ChatAdministratorRights, bot
         )
-        data["bot_administrator_rights"] = ChatAdministratorRights.de_json(
-            data.get("bot_administrator_rights"), bot
+        data["bot_administrator_rights"] = de_json_optional(
+            data.get("bot_administrator_rights"), ChatAdministratorRights, bot
         )
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_menubutton.py b/telegram/_menubutton.py
index 76c0ee49cdb..fb59a561d25 100644
--- a/telegram/_menubutton.py
+++ b/telegram/_menubutton.py
@@ -22,6 +22,7 @@
 from telegram import constants
 from telegram._telegramobject import TelegramObject
 from telegram._utils import enum
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 from telegram._webappinfo import WebAppInfo
 
@@ -69,9 +70,7 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["MenuButton"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MenuButton":
         """Converts JSON data to the appropriate :class:`MenuButton` object, i.e. takes
         care of selecting the correct subclass.
 
@@ -89,12 +88,6 @@ def de_json(
         """
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
-        if not data and cls is MenuButton:
-            return None
-
         _class_mapping: dict[str, type[MenuButton]] = {
             cls.COMMANDS: MenuButtonCommands,
             cls.WEB_APP: MenuButtonWebApp,
@@ -172,16 +165,11 @@ def __init__(self, text: str, web_app: WebAppInfo, *, api_kwargs: Optional[JSOND
             self._id_attrs = (self.type, self.text, self.web_app)
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["MenuButtonWebApp"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MenuButtonWebApp":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
+        data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot)
 
         return super().de_json(data=data, bot=bot)  # type: ignore[return-value]
 
diff --git a/telegram/_message.py b/telegram/_message.py
index df8ea36a2fc..f089851e969 100644
--- a/telegram/_message.py
+++ b/telegram/_message.py
@@ -65,7 +65,7 @@
 from telegram._story import Story
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
 from telegram._utils.entities import parse_message_entities, parse_message_entity
@@ -191,9 +191,6 @@ def _de_json(
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         if cls is MaybeInaccessibleMessage:
             if data["date"] == 0:
                 return InaccessibleMessage.de_json(data=data, bot=bot)
@@ -206,9 +203,9 @@ def _de_json(
         if data["date"] == 0:
             data["date"] = ZERO_DATE
         else:
-            data["date"] = from_timestamp(data["date"], tzinfo=loc_tzinfo)
+            data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
 
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
         return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
 
 
@@ -1251,83 +1248,100 @@ def link(self) -> Optional[str]:
         return None
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Message"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Message":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-        data["from_user"] = User.de_json(data.pop("from", None), bot)
-        data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)
-        data["entities"] = MessageEntity.de_list(data.get("entities"), bot)
-        data["caption_entities"] = MessageEntity.de_list(data.get("caption_entities"), bot)
-        data["reply_to_message"] = Message.de_json(data.get("reply_to_message"), bot)
+        data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
+        data["sender_chat"] = de_json_optional(data.get("sender_chat"), Chat, bot)
+        data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot)
+        data["caption_entities"] = de_list_optional(
+            data.get("caption_entities"), MessageEntity, bot
+        )
+        data["reply_to_message"] = de_json_optional(data.get("reply_to_message"), Message, bot)
         data["edit_date"] = from_timestamp(data.get("edit_date"), tzinfo=loc_tzinfo)
-        data["audio"] = Audio.de_json(data.get("audio"), bot)
-        data["document"] = Document.de_json(data.get("document"), bot)
-        data["animation"] = Animation.de_json(data.get("animation"), bot)
-        data["game"] = Game.de_json(data.get("game"), bot)
-        data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
-        data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
-        data["story"] = Story.de_json(data.get("story"), bot)
-        data["video"] = Video.de_json(data.get("video"), bot)
-        data["voice"] = Voice.de_json(data.get("voice"), bot)
-        data["video_note"] = VideoNote.de_json(data.get("video_note"), bot)
-        data["contact"] = Contact.de_json(data.get("contact"), bot)
-        data["location"] = Location.de_json(data.get("location"), bot)
-        data["venue"] = Venue.de_json(data.get("venue"), bot)
-        data["new_chat_members"] = User.de_list(data.get("new_chat_members"), bot)
-        data["left_chat_member"] = User.de_json(data.get("left_chat_member"), bot)
-        data["new_chat_photo"] = PhotoSize.de_list(data.get("new_chat_photo"), bot)
-        data["message_auto_delete_timer_changed"] = MessageAutoDeleteTimerChanged.de_json(
-            data.get("message_auto_delete_timer_changed"), bot
+        data["audio"] = de_json_optional(data.get("audio"), Audio, bot)
+        data["document"] = de_json_optional(data.get("document"), Document, bot)
+        data["animation"] = de_json_optional(data.get("animation"), Animation, bot)
+        data["game"] = de_json_optional(data.get("game"), Game, bot)
+        data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
+        data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
+        data["story"] = de_json_optional(data.get("story"), Story, bot)
+        data["video"] = de_json_optional(data.get("video"), Video, bot)
+        data["voice"] = de_json_optional(data.get("voice"), Voice, bot)
+        data["video_note"] = de_json_optional(data.get("video_note"), VideoNote, bot)
+        data["contact"] = de_json_optional(data.get("contact"), Contact, bot)
+        data["location"] = de_json_optional(data.get("location"), Location, bot)
+        data["venue"] = de_json_optional(data.get("venue"), Venue, bot)
+        data["new_chat_members"] = de_list_optional(data.get("new_chat_members"), User, bot)
+        data["left_chat_member"] = de_json_optional(data.get("left_chat_member"), User, bot)
+        data["new_chat_photo"] = de_list_optional(data.get("new_chat_photo"), PhotoSize, bot)
+        data["message_auto_delete_timer_changed"] = de_json_optional(
+            data.get("message_auto_delete_timer_changed"), MessageAutoDeleteTimerChanged, bot
+        )
+        data["pinned_message"] = de_json_optional(
+            data.get("pinned_message"), MaybeInaccessibleMessage, bot
+        )
+        data["invoice"] = de_json_optional(data.get("invoice"), Invoice, bot)
+        data["successful_payment"] = de_json_optional(
+            data.get("successful_payment"), SuccessfulPayment, bot
+        )
+        data["passport_data"] = de_json_optional(data.get("passport_data"), PassportData, bot)
+        data["poll"] = de_json_optional(data.get("poll"), Poll, bot)
+        data["dice"] = de_json_optional(data.get("dice"), Dice, bot)
+        data["via_bot"] = de_json_optional(data.get("via_bot"), User, bot)
+        data["proximity_alert_triggered"] = de_json_optional(
+            data.get("proximity_alert_triggered"), ProximityAlertTriggered, bot
+        )
+        data["reply_markup"] = de_json_optional(
+            data.get("reply_markup"), InlineKeyboardMarkup, bot
         )
-        data["pinned_message"] = MaybeInaccessibleMessage.de_json(data.get("pinned_message"), bot)
-        data["invoice"] = Invoice.de_json(data.get("invoice"), bot)
-        data["successful_payment"] = SuccessfulPayment.de_json(data.get("successful_payment"), bot)
-        data["passport_data"] = PassportData.de_json(data.get("passport_data"), bot)
-        data["poll"] = Poll.de_json(data.get("poll"), bot)
-        data["dice"] = Dice.de_json(data.get("dice"), bot)
-        data["via_bot"] = User.de_json(data.get("via_bot"), bot)
-        data["proximity_alert_triggered"] = ProximityAlertTriggered.de_json(
-            data.get("proximity_alert_triggered"), bot
+        data["video_chat_scheduled"] = de_json_optional(
+            data.get("video_chat_scheduled"), VideoChatScheduled, bot
         )
-        data["reply_markup"] = InlineKeyboardMarkup.de_json(data.get("reply_markup"), bot)
-        data["video_chat_scheduled"] = VideoChatScheduled.de_json(
-            data.get("video_chat_scheduled"), bot
+        data["video_chat_started"] = de_json_optional(
+            data.get("video_chat_started"), VideoChatStarted, bot
         )
-        data["video_chat_started"] = VideoChatStarted.de_json(data.get("video_chat_started"), bot)
-        data["video_chat_ended"] = VideoChatEnded.de_json(data.get("video_chat_ended"), bot)
-        data["video_chat_participants_invited"] = VideoChatParticipantsInvited.de_json(
-            data.get("video_chat_participants_invited"), bot
+        data["video_chat_ended"] = de_json_optional(
+            data.get("video_chat_ended"), VideoChatEnded, bot
         )
-        data["web_app_data"] = WebAppData.de_json(data.get("web_app_data"), bot)
-        data["forum_topic_closed"] = ForumTopicClosed.de_json(data.get("forum_topic_closed"), bot)
-        data["forum_topic_created"] = ForumTopicCreated.de_json(
-            data.get("forum_topic_created"), bot
+        data["video_chat_participants_invited"] = de_json_optional(
+            data.get("video_chat_participants_invited"), VideoChatParticipantsInvited, bot
         )
-        data["forum_topic_reopened"] = ForumTopicReopened.de_json(
-            data.get("forum_topic_reopened"), bot
+        data["web_app_data"] = de_json_optional(data.get("web_app_data"), WebAppData, bot)
+        data["forum_topic_closed"] = de_json_optional(
+            data.get("forum_topic_closed"), ForumTopicClosed, bot
         )
-        data["forum_topic_edited"] = ForumTopicEdited.de_json(data.get("forum_topic_edited"), bot)
-        data["general_forum_topic_hidden"] = GeneralForumTopicHidden.de_json(
-            data.get("general_forum_topic_hidden"), bot
+        data["forum_topic_created"] = de_json_optional(
+            data.get("forum_topic_created"), ForumTopicCreated, bot
         )
-        data["general_forum_topic_unhidden"] = GeneralForumTopicUnhidden.de_json(
-            data.get("general_forum_topic_unhidden"), bot
+        data["forum_topic_reopened"] = de_json_optional(
+            data.get("forum_topic_reopened"), ForumTopicReopened, bot
         )
-        data["write_access_allowed"] = WriteAccessAllowed.de_json(
-            data.get("write_access_allowed"), bot
+        data["forum_topic_edited"] = de_json_optional(
+            data.get("forum_topic_edited"), ForumTopicEdited, bot
+        )
+        data["general_forum_topic_hidden"] = de_json_optional(
+            data.get("general_forum_topic_hidden"), GeneralForumTopicHidden, bot
+        )
+        data["general_forum_topic_unhidden"] = de_json_optional(
+            data.get("general_forum_topic_unhidden"), GeneralForumTopicUnhidden, bot
+        )
+        data["write_access_allowed"] = de_json_optional(
+            data.get("write_access_allowed"), WriteAccessAllowed, bot
+        )
+        data["users_shared"] = de_json_optional(data.get("users_shared"), UsersShared, bot)
+        data["chat_shared"] = de_json_optional(data.get("chat_shared"), ChatShared, bot)
+        data["chat_background_set"] = de_json_optional(
+            data.get("chat_background_set"), ChatBackground, bot
+        )
+        data["paid_media"] = de_json_optional(data.get("paid_media"), PaidMediaInfo, bot)
+        data["refunded_payment"] = de_json_optional(
+            data.get("refunded_payment"), RefundedPayment, bot
         )
-        data["users_shared"] = UsersShared.de_json(data.get("users_shared"), bot)
-        data["chat_shared"] = ChatShared.de_json(data.get("chat_shared"), bot)
-        data["chat_background_set"] = ChatBackground.de_json(data.get("chat_background_set"), bot)
-        data["paid_media"] = PaidMediaInfo.de_json(data.get("paid_media"), bot)
-        data["refunded_payment"] = RefundedPayment.de_json(data.get("refunded_payment"), bot)
 
         # Unfortunately, this needs to be here due to cyclic imports
         from telegram._giveaway import (  # pylint: disable=import-outside-toplevel
@@ -1344,19 +1358,27 @@ def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optio
             TextQuote,
         )
 
-        data["giveaway"] = Giveaway.de_json(data.get("giveaway"), bot)
-        data["giveaway_completed"] = GiveawayCompleted.de_json(data.get("giveaway_completed"), bot)
-        data["giveaway_created"] = GiveawayCreated.de_json(data.get("giveaway_created"), bot)
-        data["giveaway_winners"] = GiveawayWinners.de_json(data.get("giveaway_winners"), bot)
-        data["link_preview_options"] = LinkPreviewOptions.de_json(
-            data.get("link_preview_options"), bot
+        data["giveaway"] = de_json_optional(data.get("giveaway"), Giveaway, bot)
+        data["giveaway_completed"] = de_json_optional(
+            data.get("giveaway_completed"), GiveawayCompleted, bot
+        )
+        data["giveaway_created"] = de_json_optional(
+            data.get("giveaway_created"), GiveawayCreated, bot
+        )
+        data["giveaway_winners"] = de_json_optional(
+            data.get("giveaway_winners"), GiveawayWinners, bot
+        )
+        data["link_preview_options"] = de_json_optional(
+            data.get("link_preview_options"), LinkPreviewOptions, bot
+        )
+        data["external_reply"] = de_json_optional(
+            data.get("external_reply"), ExternalReplyInfo, bot
         )
-        data["external_reply"] = ExternalReplyInfo.de_json(data.get("external_reply"), bot)
-        data["quote"] = TextQuote.de_json(data.get("quote"), bot)
-        data["forward_origin"] = MessageOrigin.de_json(data.get("forward_origin"), bot)
-        data["reply_to_story"] = Story.de_json(data.get("reply_to_story"), bot)
-        data["boost_added"] = ChatBoostAdded.de_json(data.get("boost_added"), bot)
-        data["sender_business_bot"] = User.de_json(data.get("sender_business_bot"), bot)
+        data["quote"] = de_json_optional(data.get("quote"), TextQuote, bot)
+        data["forward_origin"] = de_json_optional(data.get("forward_origin"), MessageOrigin, bot)
+        data["reply_to_story"] = de_json_optional(data.get("reply_to_story"), Story, bot)
+        data["boost_added"] = de_json_optional(data.get("boost_added"), ChatBoostAdded, bot)
+        data["sender_business_bot"] = de_json_optional(data.get("sender_business_bot"), User, bot)
 
         api_kwargs = {}
         # This is a deprecated field that TG still returns for backwards compatibility
diff --git a/telegram/_messageentity.py b/telegram/_messageentity.py
index 852c496bf92..10c90093f6d 100644
--- a/telegram/_messageentity.py
+++ b/telegram/_messageentity.py
@@ -27,6 +27,7 @@
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
 from telegram._utils import enum
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.strings import TextEncoding
 from telegram._utils.types import JSONDict
 
@@ -137,16 +138,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["MessageEntity"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MessageEntity":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["user"] = User.de_json(data.get("user"), bot)
+        data["user"] = de_json_optional(data.get("user"), User, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_messageorigin.py b/telegram/_messageorigin.py
index 0982d6501aa..9838d6bea7c 100644
--- a/telegram/_messageorigin.py
+++ b/telegram/_messageorigin.py
@@ -25,6 +25,7 @@
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
 from telegram._utils import enum
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -94,17 +95,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["MessageOrigin"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MessageOrigin":
         """Converts JSON data to the appropriate :class:`MessageOrigin` object, i.e. takes
         care of selecting the correct subclass.
         """
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         _class_mapping: dict[str, type[MessageOrigin]] = {
             cls.USER: MessageOriginUser,
             cls.HIDDEN_USER: MessageOriginHiddenUser,
@@ -118,13 +114,13 @@ def de_json(
         data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
 
         if "sender_user" in data:
-            data["sender_user"] = User.de_json(data.get("sender_user"), bot)
+            data["sender_user"] = de_json_optional(data.get("sender_user"), User, bot)
 
         if "sender_chat" in data:
-            data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)
+            data["sender_chat"] = de_json_optional(data.get("sender_chat"), Chat, bot)
 
         if "chat" in data:
-            data["chat"] = Chat.de_json(data.get("chat"), bot)
+            data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_messagereactionupdated.py b/telegram/_messagereactionupdated.py
index 105362af9e5..b1b33851454 100644
--- a/telegram/_messagereactionupdated.py
+++ b/telegram/_messagereactionupdated.py
@@ -25,7 +25,7 @@
 from telegram._reaction import ReactionCount, ReactionType
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -86,21 +86,16 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["MessageReactionCountUpdated"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MessageReactionCountUpdated":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
         data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
-        data["reactions"] = ReactionCount.de_list(data.get("reactions"), bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
+        data["reactions"] = de_list_optional(data.get("reactions"), ReactionCount, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -187,23 +182,18 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["MessageReactionUpdated"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MessageReactionUpdated":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
         data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
-        data["old_reaction"] = ReactionType.de_list(data.get("old_reaction"), bot)
-        data["new_reaction"] = ReactionType.de_list(data.get("new_reaction"), bot)
-        data["user"] = User.de_json(data.get("user"), bot)
-        data["actor_chat"] = Chat.de_json(data.get("actor_chat"), bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
+        data["old_reaction"] = de_list_optional(data.get("old_reaction"), ReactionType, bot)
+        data["new_reaction"] = de_list_optional(data.get("new_reaction"), ReactionType, bot)
+        data["user"] = de_json_optional(data.get("user"), User, bot)
+        data["actor_chat"] = de_json_optional(data.get("actor_chat"), Chat, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_paidmedia.py b/telegram/_paidmedia.py
index b649c7ec320..972c46fa333 100644
--- a/telegram/_paidmedia.py
+++ b/telegram/_paidmedia.py
@@ -27,7 +27,7 @@
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
 from telegram._utils import enum
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -75,9 +75,7 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PaidMedia"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMedia":
         """Converts JSON data to the appropriate :class:`PaidMedia` object, i.e. takes
         care of selecting the correct subclass.
 
@@ -91,12 +89,6 @@ def de_json(
         """
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
-        if not data and cls is PaidMedia:
-            return None
-
         _class_mapping: dict[str, type[PaidMedia]] = {
             cls.PREVIEW: PaidMediaPreview,
             cls.PHOTO: PaidMediaPhoto,
@@ -185,15 +177,10 @@ def __init__(
             self._id_attrs = (self.type, self.photo)
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PaidMediaPhoto"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMediaPhoto":
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["photo"] = PhotoSize.de_list(data.get("photo"), bot=bot)
+        data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
         return super().de_json(data=data, bot=bot)  # type: ignore[return-value]
 
 
@@ -231,15 +218,10 @@ def __init__(
             self._id_attrs = (self.type, self.video)
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PaidMediaVideo"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMediaVideo":
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["video"] = Video.de_json(data.get("video"), bot=bot)
+        data["video"] = de_json_optional(data.get("video"), Video, bot)
         return super().de_json(data=data, bot=bot)  # type: ignore[return-value]
 
 
@@ -280,15 +262,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PaidMediaInfo"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMediaInfo":
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot)
+        data["paid_media"] = de_list_optional(data.get("paid_media"), PaidMedia, bot)
         return super().de_json(data=data, bot=bot)
 
 
@@ -329,13 +306,8 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PaidMediaPurchased"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMediaPurchased":
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         data["from_user"] = User.de_json(data=data.pop("from"), bot=bot)
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_passport/credentials.py b/telegram/_passport/credentials.py
index 8384fde76be..11bd2d92d43 100644
--- a/telegram/_passport/credentials.py
+++ b/telegram/_passport/credentials.py
@@ -39,7 +39,7 @@
     CRYPTO_INSTALLED = False
 
 from telegram._telegramobject import TelegramObject
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.strings import TextEncoding
 from telegram._utils.types import JSONDict
 from telegram.error import PassportDecryptionError
@@ -207,7 +207,7 @@ def decrypted_data(self) -> "Credentials":
                 decrypt_json(self.decrypted_secret, b64decode(self.hash), b64decode(self.data)),
                 self.get_bot(),
             )
-        return self._decrypted_data  # type: ignore[return-value]
+        return self._decrypted_data
 
 
 class Credentials(TelegramObject):
@@ -234,16 +234,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["Credentials"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Credentials":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["secure_data"] = SecureData.de_json(data.get("secure_data"), bot=bot)
+        data["secure_data"] = de_json_optional(data.get("secure_data"), SecureData, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -346,30 +341,27 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["SecureData"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SecureData":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["temporary_registration"] = SecureValue.de_json(
-            data.get("temporary_registration"), bot=bot
+        data["temporary_registration"] = de_json_optional(
+            data.get("temporary_registration"), SecureValue, bot
         )
-        data["passport_registration"] = SecureValue.de_json(
-            data.get("passport_registration"), bot=bot
+        data["passport_registration"] = de_json_optional(
+            data.get("passport_registration"), SecureValue, bot
         )
-        data["rental_agreement"] = SecureValue.de_json(data.get("rental_agreement"), bot=bot)
-        data["bank_statement"] = SecureValue.de_json(data.get("bank_statement"), bot=bot)
-        data["utility_bill"] = SecureValue.de_json(data.get("utility_bill"), bot=bot)
-        data["address"] = SecureValue.de_json(data.get("address"), bot=bot)
-        data["identity_card"] = SecureValue.de_json(data.get("identity_card"), bot=bot)
-        data["driver_license"] = SecureValue.de_json(data.get("driver_license"), bot=bot)
-        data["internal_passport"] = SecureValue.de_json(data.get("internal_passport"), bot=bot)
-        data["passport"] = SecureValue.de_json(data.get("passport"), bot=bot)
-        data["personal_details"] = SecureValue.de_json(data.get("personal_details"), bot=bot)
+        data["rental_agreement"] = de_json_optional(data.get("rental_agreement"), SecureValue, bot)
+        data["bank_statement"] = de_json_optional(data.get("bank_statement"), SecureValue, bot)
+        data["utility_bill"] = de_json_optional(data.get("utility_bill"), SecureValue, bot)
+        data["address"] = de_json_optional(data.get("address"), SecureValue, bot)
+        data["identity_card"] = de_json_optional(data.get("identity_card"), SecureValue, bot)
+        data["driver_license"] = de_json_optional(data.get("driver_license"), SecureValue, bot)
+        data["internal_passport"] = de_json_optional(
+            data.get("internal_passport"), SecureValue, bot
+        )
+        data["passport"] = de_json_optional(data.get("passport"), SecureValue, bot)
+        data["personal_details"] = de_json_optional(data.get("personal_details"), SecureValue, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -454,21 +446,16 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["SecureValue"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SecureValue":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["data"] = DataCredentials.de_json(data.get("data"), bot=bot)
-        data["front_side"] = FileCredentials.de_json(data.get("front_side"), bot=bot)
-        data["reverse_side"] = FileCredentials.de_json(data.get("reverse_side"), bot=bot)
-        data["selfie"] = FileCredentials.de_json(data.get("selfie"), bot=bot)
-        data["files"] = FileCredentials.de_list(data.get("files"), bot=bot)
-        data["translation"] = FileCredentials.de_list(data.get("translation"), bot=bot)
+        data["data"] = de_json_optional(data.get("data"), DataCredentials, bot)
+        data["front_side"] = de_json_optional(data.get("front_side"), FileCredentials, bot)
+        data["reverse_side"] = de_json_optional(data.get("reverse_side"), FileCredentials, bot)
+        data["selfie"] = de_json_optional(data.get("selfie"), FileCredentials, bot)
+        data["files"] = de_list_optional(data.get("files"), FileCredentials, bot)
+        data["translation"] = de_list_optional(data.get("translation"), FileCredentials, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_passport/encryptedpassportelement.py b/telegram/_passport/encryptedpassportelement.py
index aa646df5839..ae7e6517ac9 100644
--- a/telegram/_passport/encryptedpassportelement.py
+++ b/telegram/_passport/encryptedpassportelement.py
@@ -25,7 +25,13 @@
 from telegram._passport.data import IdDocumentData, PersonalDetails, ResidentialAddress
 from telegram._passport.passportfile import PassportFile
 from telegram._telegramobject import TelegramObject
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import (
+    de_json_decrypted_optional,
+    de_json_optional,
+    de_list_decrypted_optional,
+    de_list_optional,
+    parse_sequence_arg,
+)
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -194,27 +200,22 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["EncryptedPassportElement"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "EncryptedPassportElement":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["files"] = PassportFile.de_list(data.get("files"), bot) or None
-        data["front_side"] = PassportFile.de_json(data.get("front_side"), bot)
-        data["reverse_side"] = PassportFile.de_json(data.get("reverse_side"), bot)
-        data["selfie"] = PassportFile.de_json(data.get("selfie"), bot)
-        data["translation"] = PassportFile.de_list(data.get("translation"), bot) or None
+        data["files"] = de_list_optional(data.get("files"), PassportFile, bot) or None
+        data["front_side"] = de_json_optional(data.get("front_side"), PassportFile, bot)
+        data["reverse_side"] = de_json_optional(data.get("reverse_side"), PassportFile, bot)
+        data["selfie"] = de_json_optional(data.get("selfie"), PassportFile, bot)
+        data["translation"] = de_list_optional(data.get("translation"), PassportFile, bot) or None
 
         return super().de_json(data=data, bot=bot)
 
     @classmethod
     def de_json_decrypted(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"], credentials: "Credentials"
-    ) -> Optional["EncryptedPassportElement"]:
+        cls, data: JSONDict, bot: Optional["Bot"], credentials: "Credentials"
+    ) -> "EncryptedPassportElement":
         """Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account
         passport credentials.
 
@@ -234,8 +235,6 @@ def de_json_decrypted(
             :class:`telegram.EncryptedPassportElement`:
 
         """
-        if not data:
-            return None
 
         if data["type"] not in ("phone_number", "email"):
             secure_data = getattr(credentials.secure_data, data["type"])
@@ -261,20 +260,21 @@ def de_json_decrypted(
                     data["data"] = ResidentialAddress.de_json(data["data"], bot=bot)
 
             data["files"] = (
-                PassportFile.de_list_decrypted(data.get("files"), bot, secure_data.files) or None
+                de_list_decrypted_optional(data.get("files"), PassportFile, bot, secure_data.files)
+                or None
             )
-            data["front_side"] = PassportFile.de_json_decrypted(
-                data.get("front_side"), bot, secure_data.front_side
+            data["front_side"] = de_json_decrypted_optional(
+                data.get("front_side"), PassportFile, bot, secure_data.front_side
             )
-            data["reverse_side"] = PassportFile.de_json_decrypted(
-                data.get("reverse_side"), bot, secure_data.reverse_side
+            data["reverse_side"] = de_json_decrypted_optional(
+                data.get("reverse_side"), PassportFile, bot, secure_data.reverse_side
             )
-            data["selfie"] = PassportFile.de_json_decrypted(
-                data.get("selfie"), bot, secure_data.selfie
+            data["selfie"] = de_json_decrypted_optional(
+                data.get("selfie"), PassportFile, bot, secure_data.selfie
             )
             data["translation"] = (
-                PassportFile.de_list_decrypted(
-                    data.get("translation"), bot, secure_data.translation
+                de_list_decrypted_optional(
+                    data.get("translation"), PassportFile, bot, secure_data.translation
                 )
                 or None
             )
diff --git a/telegram/_passport/passportdata.py b/telegram/_passport/passportdata.py
index 200a45899dc..fff227a04b6 100644
--- a/telegram/_passport/passportdata.py
+++ b/telegram/_passport/passportdata.py
@@ -23,7 +23,7 @@
 from telegram._passport.credentials import EncryptedCredentials
 from telegram._passport.encryptedpassportelement import EncryptedPassportElement
 from telegram._telegramobject import TelegramObject
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -82,17 +82,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PassportData"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PassportData":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["data"] = EncryptedPassportElement.de_list(data.get("data"), bot)
-        data["credentials"] = EncryptedCredentials.de_json(data.get("credentials"), bot)
+        data["data"] = de_list_optional(data.get("data"), EncryptedPassportElement, bot)
+        data["credentials"] = de_json_optional(data.get("credentials"), EncryptedCredentials, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_passport/passportfile.py b/telegram/_passport/passportfile.py
index c9516e7c5c3..2fe0d34e08b 100644
--- a/telegram/_passport/passportfile.py
+++ b/telegram/_passport/passportfile.py
@@ -118,8 +118,8 @@ def file_date(self) -> int:
 
     @classmethod
     def de_json_decrypted(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"], credentials: "FileCredentials"
-    ) -> Optional["PassportFile"]:
+        cls, data: JSONDict, bot: Optional["Bot"], credentials: "FileCredentials"
+    ) -> "PassportFile":
         """Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account
         passport credentials.
 
@@ -141,9 +141,6 @@ def de_json_decrypted(
         """
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         data["credentials"] = credentials
 
         return super().de_json(data=data, bot=bot)
@@ -151,10 +148,10 @@ def de_json_decrypted(
     @classmethod
     def de_list_decrypted(
         cls,
-        data: Optional[list[JSONDict]],
+        data: list[JSONDict],
         bot: Optional["Bot"],
         credentials: list["FileCredentials"],
-    ) -> tuple[Optional["PassportFile"], ...]:
+    ) -> tuple["PassportFile", ...]:
         """Variant of :meth:`telegram.TelegramObject.de_list` that also takes into account
         passport credentials.
 
@@ -179,16 +176,12 @@ def de_list_decrypted(
             tuple[:class:`telegram.PassportFile`]:
 
         """
-        if not data:
-            return ()
-
         return tuple(
             obj
             for obj in (
                 cls.de_json_decrypted(passport_file, bot, credentials[i])
                 for i, passport_file in enumerate(data)
             )
-            if obj is not None
         )
 
     async def get_file(
diff --git a/telegram/_payment/orderinfo.py b/telegram/_payment/orderinfo.py
index ddd7aef1600..ac963cacd87 100644
--- a/telegram/_payment/orderinfo.py
+++ b/telegram/_payment/orderinfo.py
@@ -22,6 +22,7 @@
 
 from telegram._payment.shippingaddress import ShippingAddress
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -71,15 +72,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["OrderInfo"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "OrderInfo":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return cls()
-
-        data["shipping_address"] = ShippingAddress.de_json(data.get("shipping_address"), bot)
+        data["shipping_address"] = de_json_optional(
+            data.get("shipping_address"), ShippingAddress, bot
+        )
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_payment/precheckoutquery.py b/telegram/_payment/precheckoutquery.py
index 766f2a4b26c..b3d2c0241e0 100644
--- a/telegram/_payment/precheckoutquery.py
+++ b/telegram/_payment/precheckoutquery.py
@@ -23,6 +23,7 @@
 from telegram._payment.orderinfo import OrderInfo
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.defaultvalue import DEFAULT_NONE
 from telegram._utils.types import JSONDict, ODVInput
 
@@ -110,17 +111,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PreCheckoutQuery"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PreCheckoutQuery":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["from_user"] = User.de_json(data.pop("from", None), bot)
-        data["order_info"] = OrderInfo.de_json(data.get("order_info"), bot)
+        data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
+        data["order_info"] = de_json_optional(data.get("order_info"), OrderInfo, bot)
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_payment/shippingquery.py b/telegram/_payment/shippingquery.py
index fadf88c0847..a31f7633de3 100644
--- a/telegram/_payment/shippingquery.py
+++ b/telegram/_payment/shippingquery.py
@@ -24,6 +24,7 @@
 from telegram._payment.shippingaddress import ShippingAddress
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.defaultvalue import DEFAULT_NONE
 from telegram._utils.types import JSONDict, ODVInput
 
@@ -78,17 +79,14 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ShippingQuery"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ShippingQuery":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["from_user"] = User.de_json(data.pop("from", None), bot)
-        data["shipping_address"] = ShippingAddress.de_json(data.get("shipping_address"), bot)
+        data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
+        data["shipping_address"] = de_json_optional(
+            data.get("shipping_address"), ShippingAddress, bot
+        )
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_payment/stars/affiliateinfo.py b/telegram/_payment/stars/affiliateinfo.py
index 9026853de7a..80349290b44 100644
--- a/telegram/_payment/stars/affiliateinfo.py
+++ b/telegram/_payment/stars/affiliateinfo.py
@@ -22,6 +22,7 @@
 from telegram._chat import Chat
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -105,16 +106,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["AffiliateInfo"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "AffiliateInfo":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["affiliate_user"] = User.de_json(data.get("affiliate_user"), bot)
-        data["affiliate_chat"] = Chat.de_json(data.get("affiliate_chat"), bot)
+        data["affiliate_user"] = de_json_optional(data.get("affiliate_user"), User, bot)
+        data["affiliate_chat"] = de_json_optional(data.get("affiliate_chat"), Chat, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_payment/stars/revenuewithdrawalstate.py b/telegram/_payment/stars/revenuewithdrawalstate.py
index be75ae4c6c0..db4f2527706 100644
--- a/telegram/_payment/stars/revenuewithdrawalstate.py
+++ b/telegram/_payment/stars/revenuewithdrawalstate.py
@@ -68,9 +68,7 @@ def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["RevenueWithdrawalState"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "RevenueWithdrawalState":
         """Converts JSON data to the appropriate :class:`RevenueWithdrawalState` object, i.e. takes
         care of selecting the correct subclass.
 
@@ -84,9 +82,6 @@ def de_json(
         """
         data = cls._parse_data(data)
 
-        if (cls is RevenueWithdrawalState and not data) or data is None:
-            return None
-
         _class_mapping: dict[str, type[RevenueWithdrawalState]] = {
             cls.PENDING: RevenueWithdrawalStatePending,
             cls.SUCCEEDED: RevenueWithdrawalStateSucceeded,
@@ -156,14 +151,11 @@ def __init__(
 
     @classmethod
     def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["RevenueWithdrawalStateSucceeded"]:
+        cls, data: JSONDict, bot: Optional["Bot"] = None
+    ) -> "RevenueWithdrawalStateSucceeded":
         """See :meth:`telegram.RevenueWithdrawalState.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
         data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
diff --git a/telegram/_payment/stars/startransactions.py b/telegram/_payment/stars/startransactions.py
index f53a0d1a660..6578cdd5a48 100644
--- a/telegram/_payment/stars/startransactions.py
+++ b/telegram/_payment/stars/startransactions.py
@@ -24,7 +24,7 @@
 from typing import TYPE_CHECKING, Optional
 
 from telegram._telegramobject import TelegramObject
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -112,21 +112,16 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["StarTransaction"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "StarTransaction":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
         data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
 
-        data["source"] = TransactionPartner.de_json(data.get("source"), bot)
-        data["receiver"] = TransactionPartner.de_json(data.get("receiver"), bot)
+        data["source"] = de_json_optional(data.get("source"), TransactionPartner, bot)
+        data["receiver"] = de_json_optional(data.get("receiver"), TransactionPartner, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -159,14 +154,9 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["StarTransactions"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "StarTransactions":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
-        data["transactions"] = StarTransaction.de_list(data.get("transactions"), bot)
+        data["transactions"] = de_list_optional(data.get("transactions"), StarTransaction, bot)
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_payment/stars/transactionpartner.py b/telegram/_payment/stars/transactionpartner.py
index 7c82bf0c0f3..860fccff17d 100644
--- a/telegram/_payment/stars/transactionpartner.py
+++ b/telegram/_payment/stars/transactionpartner.py
@@ -28,7 +28,7 @@
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
 from telegram._utils import enum
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.types import JSONDict
 
 from .affiliateinfo import AffiliateInfo
@@ -87,9 +87,7 @@ def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["TransactionPartner"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TransactionPartner":
         """Converts JSON data to the appropriate :class:`TransactionPartner` object, i.e. takes
         care of selecting the correct subclass.
 
@@ -103,9 +101,6 @@ def de_json(
         """
         data = cls._parse_data(data)
 
-        if (cls is TransactionPartner and not data) or data is None:
-            return None
-
         _class_mapping: dict[str, type[TransactionPartner]] = {
             cls.AFFILIATE_PROGRAM: TransactionPartnerAffiliateProgram,
             cls.FRAGMENT: TransactionPartnerFragment,
@@ -166,15 +161,12 @@ def __init__(
 
     @classmethod
     def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["TransactionPartnerAffiliateProgram"]:
+        cls, data: JSONDict, bot: Optional["Bot"] = None
+    ) -> "TransactionPartnerAffiliateProgram":
         """See :meth:`telegram.TransactionPartner.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["sponsor_user"] = User.de_json(data.get("sponsor_user"), bot)
+        data["sponsor_user"] = de_json_optional(data.get("sponsor_user"), User, bot)
 
         return super().de_json(data=data, bot=bot)  # type: ignore[return-value]
 
@@ -209,17 +201,12 @@ def __init__(
             self.withdrawal_state: Optional[RevenueWithdrawalState] = withdrawal_state
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["TransactionPartnerFragment"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TransactionPartnerFragment":
         """See :meth:`telegram.TransactionPartner.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
-        data["withdrawal_state"] = RevenueWithdrawalState.de_json(
-            data.get("withdrawal_state"), bot
+        data["withdrawal_state"] = de_json_optional(
+            data.get("withdrawal_state"), RevenueWithdrawalState, bot
         )
 
         return super().de_json(data=data, bot=bot)  # type: ignore[return-value]
@@ -320,24 +307,19 @@ def __init__(
             )
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["TransactionPartnerUser"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TransactionPartnerUser":
         """See :meth:`telegram.TransactionPartner.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["user"] = User.de_json(data.get("user"), bot)
-        data["affiliate"] = AffiliateInfo.de_json(data.get("affiliate"), bot)
-        data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot)
+        data["user"] = de_json_optional(data.get("user"), User, bot)
+        data["affiliate"] = de_json_optional(data.get("affiliate"), AffiliateInfo, bot)
+        data["paid_media"] = de_list_optional(data.get("paid_media"), PaidMedia, bot)
         data["subscription_period"] = (
             dtm.timedelta(seconds=sp)
             if (sp := data.get("subscription_period")) is not None
             else None
         )
-        data["gift"] = Gift.de_json(data.get("gift"), bot)
+        data["gift"] = de_json_optional(data.get("gift"), Gift, bot)
 
         return super().de_json(data=data, bot=bot)  # type: ignore[return-value]
 
diff --git a/telegram/_payment/successfulpayment.py b/telegram/_payment/successfulpayment.py
index 0642e302c21..e7e72a24e3b 100644
--- a/telegram/_payment/successfulpayment.py
+++ b/telegram/_payment/successfulpayment.py
@@ -23,6 +23,7 @@
 
 from telegram._payment.orderinfo import OrderInfo
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
 
@@ -138,16 +139,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["SuccessfulPayment"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuccessfulPayment":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["order_info"] = OrderInfo.de_json(data.get("order_info"), bot)
+        data["order_info"] = de_json_optional(data.get("order_info"), OrderInfo, bot)
 
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
diff --git a/telegram/_poll.py b/telegram/_poll.py
index f26ac8476e6..8ecdc4105f9 100644
--- a/telegram/_poll.py
+++ b/telegram/_poll.py
@@ -27,7 +27,7 @@
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
 from telegram._utils import enum
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.defaultvalue import DEFAULT_NONE
 from telegram._utils.entities import parse_message_entities, parse_message_entity
@@ -91,16 +91,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["InputPollOption"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InputPollOption":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
+        data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -157,16 +152,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PollOption"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PollOption":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
+        data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -306,17 +296,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["PollAnswer"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PollAnswer":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["user"] = User.de_json(data.get("user"), bot)
-        data["voter_chat"] = Chat.de_json(data.get("voter_chat"), bot)
+        data["user"] = de_json_optional(data.get("user"), User, bot)
+        data["voter_chat"] = de_json_optional(data.get("voter_chat"), Chat, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -474,20 +459,21 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Poll"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Poll":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-        data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
-        data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
+        data["options"] = de_list_optional(data.get("options"), PollOption, bot)
+        data["explanation_entities"] = de_list_optional(
+            data.get("explanation_entities"), MessageEntity, bot
+        )
         data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)
-        data["question_entities"] = MessageEntity.de_list(data.get("question_entities"), bot)
+        data["question_entities"] = de_list_optional(
+            data.get("question_entities"), MessageEntity, bot
+        )
 
         return super().de_json(data=data, bot=bot)
 
diff --git a/telegram/_proximityalerttriggered.py b/telegram/_proximityalerttriggered.py
index b4ea6a26b5e..c9e00ef1bf0 100644
--- a/telegram/_proximityalerttriggered.py
+++ b/telegram/_proximityalerttriggered.py
@@ -21,6 +21,7 @@
 
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -67,16 +68,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ProximityAlertTriggered"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ProximityAlertTriggered":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["traveler"] = User.de_json(data.get("traveler"), bot)
-        data["watcher"] = User.de_json(data.get("watcher"), bot)
+        data["traveler"] = de_json_optional(data.get("traveler"), User, bot)
+        data["watcher"] = de_json_optional(data.get("watcher"), User, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_reaction.py b/telegram/_reaction.py
index 7d76b82efbf..6e1e3fb79af 100644
--- a/telegram/_reaction.py
+++ b/telegram/_reaction.py
@@ -23,6 +23,7 @@
 from telegram import constants
 from telegram._telegramobject import TelegramObject
 from telegram._utils import enum
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -77,18 +78,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ReactionType"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ReactionType":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
-        if not data and cls is ReactionType:
-            return None
-
         _class_mapping: dict[str, type[ReactionType]] = {
             cls.EMOJI: ReactionTypeEmoji,
             cls.CUSTOM_EMOJI: ReactionTypeCustomEmoji,
@@ -230,15 +223,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ReactionCount"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ReactionCount":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["type"] = ReactionType.de_json(data.get("type"), bot)
+        data["type"] = de_json_optional(data.get("type"), ReactionType, bot)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_reply.py b/telegram/_reply.py
index ba04b892ca6..ca6b23b0507 100644
--- a/telegram/_reply.py
+++ b/telegram/_reply.py
@@ -43,7 +43,7 @@
 from telegram._poll import Poll
 from telegram._story import Story
 from telegram._telegramobject import TelegramObject
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
 from telegram._utils.defaultvalue import DEFAULT_NONE
 from telegram._utils.types import JSONDict, ODVInput
 
@@ -248,39 +248,36 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ExternalReplyInfo"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ExternalReplyInfo":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
-        data["origin"] = MessageOrigin.de_json(data.get("origin"), bot)
-        data["chat"] = Chat.de_json(data.get("chat"), bot)
-        data["link_preview_options"] = LinkPreviewOptions.de_json(
-            data.get("link_preview_options"), bot
+        data["origin"] = de_json_optional(data.get("origin"), MessageOrigin, bot)
+        data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
+        data["link_preview_options"] = de_json_optional(
+            data.get("link_preview_options"), LinkPreviewOptions, bot
+        )
+        data["animation"] = de_json_optional(data.get("animation"), Animation, bot)
+        data["audio"] = de_json_optional(data.get("audio"), Audio, bot)
+        data["document"] = de_json_optional(data.get("document"), Document, bot)
+        data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
+        data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
+        data["story"] = de_json_optional(data.get("story"), Story, bot)
+        data["video"] = de_json_optional(data.get("video"), Video, bot)
+        data["video_note"] = de_json_optional(data.get("video_note"), VideoNote, bot)
+        data["voice"] = de_json_optional(data.get("voice"), Voice, bot)
+        data["contact"] = de_json_optional(data.get("contact"), Contact, bot)
+        data["dice"] = de_json_optional(data.get("dice"), Dice, bot)
+        data["game"] = de_json_optional(data.get("game"), Game, bot)
+        data["giveaway"] = de_json_optional(data.get("giveaway"), Giveaway, bot)
+        data["giveaway_winners"] = de_json_optional(
+            data.get("giveaway_winners"), GiveawayWinners, bot
         )
-        data["animation"] = Animation.de_json(data.get("animation"), bot)
-        data["audio"] = Audio.de_json(data.get("audio"), bot)
-        data["document"] = Document.de_json(data.get("document"), bot)
-        data["photo"] = tuple(PhotoSize.de_list(data.get("photo"), bot))
-        data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
-        data["story"] = Story.de_json(data.get("story"), bot)
-        data["video"] = Video.de_json(data.get("video"), bot)
-        data["video_note"] = VideoNote.de_json(data.get("video_note"), bot)
-        data["voice"] = Voice.de_json(data.get("voice"), bot)
-        data["contact"] = Contact.de_json(data.get("contact"), bot)
-        data["dice"] = Dice.de_json(data.get("dice"), bot)
-        data["game"] = Game.de_json(data.get("game"), bot)
-        data["giveaway"] = Giveaway.de_json(data.get("giveaway"), bot)
-        data["giveaway_winners"] = GiveawayWinners.de_json(data.get("giveaway_winners"), bot)
-        data["invoice"] = Invoice.de_json(data.get("invoice"), bot)
-        data["location"] = Location.de_json(data.get("location"), bot)
-        data["poll"] = Poll.de_json(data.get("poll"), bot)
-        data["venue"] = Venue.de_json(data.get("venue"), bot)
-        data["paid_media"] = PaidMediaInfo.de_json(data.get("paid_media"), bot)
+        data["invoice"] = de_json_optional(data.get("invoice"), Invoice, bot)
+        data["location"] = de_json_optional(data.get("location"), Location, bot)
+        data["poll"] = de_json_optional(data.get("poll"), Poll, bot)
+        data["venue"] = de_json_optional(data.get("venue"), Venue, bot)
+        data["paid_media"] = de_json_optional(data.get("paid_media"), PaidMediaInfo, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -350,16 +347,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["TextQuote"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TextQuote":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
-        data["entities"] = tuple(MessageEntity.de_list(data.get("entities"), bot))
+        data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot)
 
         return super().de_json(data=data, bot=bot)
 
@@ -458,15 +450,12 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ReplyParameters"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ReplyParameters":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if data is None:
-            return None
-
-        data["quote_entities"] = tuple(MessageEntity.de_list(data.get("quote_entities"), bot))
+        data["quote_entities"] = tuple(
+            de_list_optional(data.get("quote_entities"), MessageEntity, bot)
+        )
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_shared.py b/telegram/_shared.py
index 5ba54ea99e2..9c0d3684ec2 100644
--- a/telegram/_shared.py
+++ b/telegram/_shared.py
@@ -22,7 +22,7 @@
 
 from telegram._files.photosize import PhotoSize
 from telegram._telegramobject import TelegramObject
-from telegram._utils.argumentparsing import parse_sequence_arg
+from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg
 from telegram._utils.types import JSONDict
 
 if TYPE_CHECKING:
@@ -84,16 +84,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["UsersShared"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "UsersShared":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["users"] = SharedUser.de_list(data.get("users"), bot)
+        data["users"] = de_list_optional(data.get("users"), SharedUser, bot)
 
         api_kwargs = {}
         # This is a deprecated field that TG still returns for backwards compatibility
@@ -175,16 +170,11 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["ChatShared"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatShared":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
+        data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
         return super().de_json(data=data, bot=bot)
 
 
@@ -255,14 +245,9 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["SharedUser"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SharedUser":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
+        data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_story.py b/telegram/_story.py
index 06832e08b87..8d14b553067 100644
--- a/telegram/_story.py
+++ b/telegram/_story.py
@@ -71,12 +71,9 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Story"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Story":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         data["chat"] = Chat.de_json(data.get("chat", {}), bot)
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_telegramobject.py b/telegram/_telegramobject.py
index baa739e410e..1b1b30ed092 100644
--- a/telegram/_telegramobject.py
+++ b/telegram/_telegramobject.py
@@ -377,23 +377,20 @@ def __deepcopy__(self: Tele_co, memodict: dict[int, object]) -> Tele_co:
         return result
 
     @staticmethod
-    def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
+    def _parse_data(data: JSONDict) -> JSONDict:
         """Should be called by subclasses that override de_json to ensure that the input
         is not altered. Whoever calls de_json might still want to use the original input
         for something else.
         """
-        return None if data is None else data.copy()
+        return data.copy()
 
     @classmethod
     def _de_json(
         cls: type[Tele_co],
-        data: Optional[JSONDict],
+        data: JSONDict,
         bot: Optional["Bot"],
         api_kwargs: Optional[JSONDict] = None,
-    ) -> Optional[Tele_co]:
-        if data is None:
-            return None
-
+    ) -> Tele_co:
         # try-except is significantly faster in case we already have a correct argument set
         try:
             obj = cls(**data, api_kwargs=api_kwargs)
@@ -417,9 +414,7 @@ def _de_json(
         return obj
 
     @classmethod
-    def de_json(
-        cls: type[Tele_co], data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional[Tele_co]:
+    def de_json(cls: type[Tele_co], data: JSONDict, bot: Optional["Bot"] = None) -> Tele_co:
         """Converts JSON data to a Telegram object.
 
         Args:
@@ -438,7 +433,7 @@ def de_json(
 
     @classmethod
     def de_list(
-        cls: type[Tele_co], data: Optional[list[JSONDict]], bot: Optional["Bot"] = None
+        cls: type[Tele_co], data: list[JSONDict], bot: Optional["Bot"] = None
     ) -> tuple[Tele_co, ...]:
         """Converts a list of JSON objects to a tuple of Telegram objects.
 
@@ -459,10 +454,7 @@ def de_list(
             A tuple of Telegram objects.
 
         """
-        if not data:
-            return ()
-
-        return tuple(obj for obj in (cls.de_json(d, bot) for d in data) if obj is not None)
+        return tuple(cls.de_json(d, bot) for d in data)
 
     @contextmanager
     def _unfrozen(self: Tele_co) -> Iterator[Tele_co]:
diff --git a/telegram/_update.py b/telegram/_update.py
index 34f8fe2749a..d1627ff81d3 100644
--- a/telegram/_update.py
+++ b/telegram/_update.py
@@ -35,6 +35,7 @@
 from telegram._payment.shippingquery import ShippingQuery
 from telegram._poll import Poll, PollAnswer
 from telegram._telegramobject import TelegramObject
+from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.types import JSONDict
 from telegram._utils.warnings import warn
 
@@ -757,47 +758,56 @@ def effective_message(self) -> Optional[Message]:
         return message
 
     @classmethod
-    def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Update"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Update":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
-        data["message"] = Message.de_json(data.get("message"), bot)
-        data["edited_message"] = Message.de_json(data.get("edited_message"), bot)
-        data["inline_query"] = InlineQuery.de_json(data.get("inline_query"), bot)
-        data["chosen_inline_result"] = ChosenInlineResult.de_json(
-            data.get("chosen_inline_result"), bot
+        data["message"] = de_json_optional(data.get("message"), Message, bot)
+        data["edited_message"] = de_json_optional(data.get("edited_message"), Message, bot)
+        data["inline_query"] = de_json_optional(data.get("inline_query"), InlineQuery, bot)
+        data["chosen_inline_result"] = de_json_optional(
+            data.get("chosen_inline_result"), ChosenInlineResult, bot
+        )
+        data["callback_query"] = de_json_optional(data.get("callback_query"), CallbackQuery, bot)
+        data["shipping_query"] = de_json_optional(data.get("shipping_query"), ShippingQuery, bot)
+        data["pre_checkout_query"] = de_json_optional(
+            data.get("pre_checkout_query"), PreCheckoutQuery, bot
+        )
+        data["channel_post"] = de_json_optional(data.get("channel_post"), Message, bot)
+        data["edited_channel_post"] = de_json_optional(
+            data.get("edited_channel_post"), Message, bot
+        )
+        data["poll"] = de_json_optional(data.get("poll"), Poll, bot)
+        data["poll_answer"] = de_json_optional(data.get("poll_answer"), PollAnswer, bot)
+        data["my_chat_member"] = de_json_optional(
+            data.get("my_chat_member"), ChatMemberUpdated, bot
+        )
+        data["chat_member"] = de_json_optional(data.get("chat_member"), ChatMemberUpdated, bot)
+        data["chat_join_request"] = de_json_optional(
+            data.get("chat_join_request"), ChatJoinRequest, bot
+        )
+        data["chat_boost"] = de_json_optional(data.get("chat_boost"), ChatBoostUpdated, bot)
+        data["removed_chat_boost"] = de_json_optional(
+            data.get("removed_chat_boost"), ChatBoostRemoved, bot
+        )
+        data["message_reaction"] = de_json_optional(
+            data.get("message_reaction"), MessageReactionUpdated, bot
         )
-        data["callback_query"] = CallbackQuery.de_json(data.get("callback_query"), bot)
-        data["shipping_query"] = ShippingQuery.de_json(data.get("shipping_query"), bot)
-        data["pre_checkout_query"] = PreCheckoutQuery.de_json(data.get("pre_checkout_query"), bot)
-        data["channel_post"] = Message.de_json(data.get("channel_post"), bot)
-        data["edited_channel_post"] = Message.de_json(data.get("edited_channel_post"), bot)
-        data["poll"] = Poll.de_json(data.get("poll"), bot)
-        data["poll_answer"] = PollAnswer.de_json(data.get("poll_answer"), bot)
-        data["my_chat_member"] = ChatMemberUpdated.de_json(data.get("my_chat_member"), bot)
-        data["chat_member"] = ChatMemberUpdated.de_json(data.get("chat_member"), bot)
-        data["chat_join_request"] = ChatJoinRequest.de_json(data.get("chat_join_request"), bot)
-        data["chat_boost"] = ChatBoostUpdated.de_json(data.get("chat_boost"), bot)
-        data["removed_chat_boost"] = ChatBoostRemoved.de_json(data.get("removed_chat_boost"), bot)
-        data["message_reaction"] = MessageReactionUpdated.de_json(
-            data.get("message_reaction"), bot
+        data["message_reaction_count"] = de_json_optional(
+            data.get("message_reaction_count"), MessageReactionCountUpdated, bot
         )
-        data["message_reaction_count"] = MessageReactionCountUpdated.de_json(
-            data.get("message_reaction_count"), bot
+        data["business_connection"] = de_json_optional(
+            data.get("business_connection"), BusinessConnection, bot
         )
-        data["business_connection"] = BusinessConnection.de_json(
-            data.get("business_connection"), bot
+        data["business_message"] = de_json_optional(data.get("business_message"), Message, bot)
+        data["edited_business_message"] = de_json_optional(
+            data.get("edited_business_message"), Message, bot
         )
-        data["business_message"] = Message.de_json(data.get("business_message"), bot)
-        data["edited_business_message"] = Message.de_json(data.get("edited_business_message"), bot)
-        data["deleted_business_messages"] = BusinessMessagesDeleted.de_json(
-            data.get("deleted_business_messages"), bot
+        data["deleted_business_messages"] = de_json_optional(
+            data.get("deleted_business_messages"), BusinessMessagesDeleted, bot
         )
-        data["purchased_paid_media"] = PaidMediaPurchased.de_json(
-            data.get("purchased_paid_media"), bot
+        data["purchased_paid_media"] = de_json_optional(
+            data.get("purchased_paid_media"), PaidMediaPurchased, bot
         )
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_userprofilephotos.py b/telegram/_userprofilephotos.py
index cc812d537ba..95344c1be5f 100644
--- a/telegram/_userprofilephotos.py
+++ b/telegram/_userprofilephotos.py
@@ -71,15 +71,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["UserProfilePhotos"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "UserProfilePhotos":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         data["photos"] = [PhotoSize.de_list(photo, bot) for photo in data["photos"]]
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_utils/argumentparsing.py b/telegram/_utils/argumentparsing.py
index e72848a9ef9..84ca1bc6a2f 100644
--- a/telegram/_utils/argumentparsing.py
+++ b/telegram/_utils/argumentparsing.py
@@ -24,10 +24,16 @@
     the changelog.
 """
 from collections.abc import Sequence
-from typing import Optional, TypeVar
+from typing import TYPE_CHECKING, Optional, Protocol, TypeVar
 
 from telegram._linkpreviewoptions import LinkPreviewOptions
-from telegram._utils.types import ODVInput
+from telegram._telegramobject import TelegramObject
+from telegram._utils.types import JSONDict, ODVInput
+
+if TYPE_CHECKING:
+    from typing import type_check_only
+
+    from telegram import Bot, FileCredentials
 
 T = TypeVar("T")
 
@@ -60,3 +66,75 @@ def parse_lpo_and_dwpp(
         link_preview_options = LinkPreviewOptions(is_disabled=disable_web_page_preview)
 
     return link_preview_options
+
+
+Tele_co = TypeVar("Tele_co", bound=TelegramObject, covariant=True)
+TeleCrypto_co = TypeVar("TeleCrypto_co", bound="HasDecryptMethod", covariant=True)
+
+if TYPE_CHECKING:
+
+    @type_check_only
+    class HasDecryptMethod(Protocol):
+        __slots__ = ()
+
+        @classmethod
+        def de_json_decrypted(
+            cls: type[TeleCrypto_co],
+            data: JSONDict,
+            bot: Optional["Bot"],
+            credentials: list["FileCredentials"],
+        ) -> TeleCrypto_co: ...
+
+        @classmethod
+        def de_list_decrypted(
+            cls: type[TeleCrypto_co],
+            data: list[JSONDict],
+            bot: Optional["Bot"],
+            credentials: list["FileCredentials"],
+        ) -> tuple[TeleCrypto_co, ...]: ...
+
+
+def de_json_optional(
+    data: Optional[JSONDict], cls: type[Tele_co], bot: Optional["Bot"]
+) -> Optional[Tele_co]:
+    """Wrapper around TO.de_json that returns None if data is None."""
+    if data is None:
+        return None
+
+    return cls.de_json(data, bot)
+
+
+def de_json_decrypted_optional(
+    data: Optional[JSONDict],
+    cls: type[TeleCrypto_co],
+    bot: Optional["Bot"],
+    credentials: list["FileCredentials"],
+) -> Optional[TeleCrypto_co]:
+    """Wrapper around TO.de_json_decrypted that returns None if data is None."""
+    if data is None:
+        return None
+
+    return cls.de_json_decrypted(data, bot, credentials)
+
+
+def de_list_optional(
+    data: Optional[list[JSONDict]], cls: type[Tele_co], bot: Optional["Bot"]
+) -> tuple[Tele_co, ...]:
+    """Wrapper around TO.de_list that returns an empty list if data is None."""
+    if data is None:
+        return ()
+
+    return cls.de_list(data, bot)
+
+
+def de_list_decrypted_optional(
+    data: Optional[list[JSONDict]],
+    cls: type[TeleCrypto_co],
+    bot: Optional["Bot"],
+    credentials: list["FileCredentials"],
+) -> tuple[TeleCrypto_co, ...]:
+    """Wrapper around TO.de_list_decrypted that returns an empty list if data is None."""
+    if data is None:
+        return ()
+
+    return cls.de_list_decrypted(data, bot, credentials)
diff --git a/telegram/_videochat.py b/telegram/_videochat.py
index 77da89343a2..7c1ec00aabb 100644
--- a/telegram/_videochat.py
+++ b/telegram/_videochat.py
@@ -126,14 +126,11 @@ def __init__(
 
     @classmethod
     def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["VideoChatParticipantsInvited"]:
+        cls, data: JSONDict, bot: Optional["Bot"] = None
+    ) -> "VideoChatParticipantsInvited":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         data["users"] = User.de_list(data.get("users", []), bot)
         return super().de_json(data=data, bot=bot)
 
@@ -178,18 +175,13 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["VideoChatScheduled"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "VideoChatScheduled":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 
-        data["start_date"] = from_timestamp(data["start_date"], tzinfo=loc_tzinfo)
+        data["start_date"] = from_timestamp(data.get("start_date"), tzinfo=loc_tzinfo)
 
         return super().de_json(data=data, bot=bot)
diff --git a/telegram/_webhookinfo.py b/telegram/_webhookinfo.py
index 5f46e647f2c..b2cca87dea1 100644
--- a/telegram/_webhookinfo.py
+++ b/telegram/_webhookinfo.py
@@ -166,15 +166,10 @@ def __init__(
         self._freeze()
 
     @classmethod
-    def de_json(
-        cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
-    ) -> Optional["WebhookInfo"]:
+    def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "WebhookInfo":
         """See :meth:`telegram.TelegramObject.de_json`."""
         data = cls._parse_data(data)
 
-        if not data:
-            return None
-
         # Get the local timezone from the bot if it has defaults
         loc_tzinfo = extract_tzinfo_from_defaults(bot)
 


Test patch:
diff --git a/tests/_files/test_animation.py b/tests/_files/test_animation.py
index cbdc8b5a7ca..62c7e79ab17 100644
--- a/tests/_files/test_animation.py
+++ b/tests/_files/test_animation.py
@@ -138,7 +138,9 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
         )
 
     @pytest.mark.parametrize("local_mode", [True, False])
-    async def test_send_animation_local_files(self, monkeypatch, offline_bot, chat_id, local_mode):
+    async def test_send_animation_local_files(
+        self, monkeypatch, offline_bot, chat_id, local_mode, dummy_message_dict
+    ):
         try:
             offline_bot._local_mode = local_mode
             # For just test that the correct paths are passed as we have no local Bot API set up
@@ -156,6 +158,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     test_flag = isinstance(data.get("animation"), InputFile) and isinstance(
                         data.get("thumbnail"), InputFile
                     )
+                return dummy_message_dict
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.send_animation(chat_id, file, thumbnail=file)
diff --git a/tests/_files/test_audio.py b/tests/_files/test_audio.py
index afdd8c75432..108cd1d10d4 100644
--- a/tests/_files/test_audio.py
+++ b/tests/_files/test_audio.py
@@ -150,7 +150,9 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
         assert await offline_bot.send_audio(chat_id, audio_file, filename="custom_filename")
 
     @pytest.mark.parametrize("local_mode", [True, False])
-    async def test_send_audio_local_files(self, monkeypatch, offline_bot, chat_id, local_mode):
+    async def test_send_audio_local_files(
+        self, dummy_message_dict, monkeypatch, offline_bot, chat_id, local_mode
+    ):
         try:
             offline_bot._local_mode = local_mode
             # For just test that the correct paths are passed as we have no local Bot API set up
@@ -166,6 +168,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     test_flag = isinstance(data.get("audio"), InputFile) and isinstance(
                         data.get("thumbnail"), InputFile
                     )
+                return dummy_message_dict
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.send_audio(chat_id, file, thumbnail=file)
diff --git a/tests/_files/test_document.py b/tests/_files/test_document.py
index 71ce508e4fd..386214c1a9f 100644
--- a/tests/_files/test_document.py
+++ b/tests/_files/test_document.py
@@ -169,7 +169,9 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
         )
 
     @pytest.mark.parametrize("local_mode", [True, False])
-    async def test_send_document_local_files(self, monkeypatch, offline_bot, chat_id, local_mode):
+    async def test_send_document_local_files(
+        self, dummy_message_dict, monkeypatch, offline_bot, chat_id, local_mode
+    ):
         try:
             offline_bot._local_mode = local_mode
             # For just test that the correct paths are passed as we have no local Bot API set up
@@ -187,6 +189,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     test_flag = isinstance(data.get("document"), InputFile) and isinstance(
                         data.get("thumbnail"), InputFile
                     )
+                return dummy_message_dict
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.send_document(chat_id, file, thumbnail=file)
diff --git a/tests/_files/test_photo.py b/tests/_files/test_photo.py
index 961bd71c8dc..dccfce43547 100644
--- a/tests/_files/test_photo.py
+++ b/tests/_files/test_photo.py
@@ -144,7 +144,9 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
         assert await offline_bot.send_photo(chat_id, photo_file, filename="custom_filename")
 
     @pytest.mark.parametrize("local_mode", [True, False])
-    async def test_send_photo_local_files(self, monkeypatch, offline_bot, chat_id, local_mode):
+    async def test_send_photo_local_files(
+        self, dummy_message_dict, monkeypatch, offline_bot, chat_id, local_mode
+    ):
         try:
             offline_bot._local_mode = local_mode
             # For just test that the correct paths are passed as we have no local Bot API set up
@@ -158,6 +160,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     test_flag = data.get("photo") == expected
                 else:
                     test_flag = isinstance(data.get("photo"), InputFile)
+                return dummy_message_dict
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.send_photo(chat_id, file)
diff --git a/tests/_files/test_sticker.py b/tests/_files/test_sticker.py
index a10611fab35..0d599657c78 100644
--- a/tests/_files/test_sticker.py
+++ b/tests/_files/test_sticker.py
@@ -256,7 +256,9 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
         assert await offline_bot.send_sticker(sticker=sticker, chat_id=chat_id)
 
     @pytest.mark.parametrize("local_mode", [True, False])
-    async def test_send_sticker_local_files(self, monkeypatch, offline_bot, chat_id, local_mode):
+    async def test_send_sticker_local_files(
+        self, dummy_message_dict, monkeypatch, offline_bot, chat_id, local_mode
+    ):
         try:
             offline_bot._local_mode = local_mode
             # For just test that the correct paths are passed as we have no local Bot API set up
@@ -270,6 +272,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     test_flag = data.get("sticker") == expected
                 else:
                     test_flag = isinstance(data.get("sticker"), InputFile)
+                return dummy_message_dict
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.send_sticker(chat_id, file)
@@ -581,6 +584,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     if local_mode
                     else isinstance(data.get("sticker"), InputFile)
                 )
+                return File(file_id="file_id", file_unique_id="file_unique_id").to_dict()
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.upload_sticker_file(
diff --git a/tests/_files/test_video.py b/tests/_files/test_video.py
index 97198d46ecd..e53e8c6ba0b 100644
--- a/tests/_files/test_video.py
+++ b/tests/_files/test_video.py
@@ -157,7 +157,9 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
         assert await offline_bot.send_video(chat_id, video_file, filename="custom_filename")
 
     @pytest.mark.parametrize("local_mode", [True, False])
-    async def test_send_video_local_files(self, monkeypatch, offline_bot, chat_id, local_mode):
+    async def test_send_video_local_files(
+        self, dummy_message_dict, monkeypatch, offline_bot, chat_id, local_mode
+    ):
         try:
             offline_bot._local_mode = local_mode
             # For just test that the correct paths are passed as we have no local Bot API set up
@@ -173,6 +175,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     test_flag = isinstance(data.get("video"), InputFile) and isinstance(
                         data.get("thumbnail"), InputFile
                     )
+                return dummy_message_dict
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.send_video(chat_id, file, thumbnail=file)
diff --git a/tests/_files/test_videonote.py b/tests/_files/test_videonote.py
index b639f968b87..38d6b7dd280 100644
--- a/tests/_files/test_videonote.py
+++ b/tests/_files/test_videonote.py
@@ -157,7 +157,7 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
 
     @pytest.mark.parametrize("local_mode", [True, False])
     async def test_send_video_note_local_files(
-        self, monkeypatch, offline_bot, chat_id, local_mode
+        self, monkeypatch, offline_bot, chat_id, local_mode, dummy_message_dict
     ):
         try:
             offline_bot._local_mode = local_mode
@@ -176,6 +176,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     test_flag = isinstance(data.get("video_note"), InputFile) and isinstance(
                         data.get("thumbnail"), InputFile
                     )
+                return dummy_message_dict
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.send_video_note(chat_id, file, thumbnail=file)
diff --git a/tests/_files/test_voice.py b/tests/_files/test_voice.py
index ccba583de4f..c5fa99094bd 100644
--- a/tests/_files/test_voice.py
+++ b/tests/_files/test_voice.py
@@ -145,7 +145,9 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
         assert await offline_bot.send_voice(chat_id, voice=voice)
 
     @pytest.mark.parametrize("local_mode", [True, False])
-    async def test_send_voice_local_files(self, monkeypatch, offline_bot, chat_id, local_mode):
+    async def test_send_voice_local_files(
+        self, dummy_message_dict, monkeypatch, offline_bot, chat_id, local_mode
+    ):
         try:
             offline_bot._local_mode = local_mode
             # For just test that the correct paths are passed as we have no local Bot API set up
@@ -159,6 +161,7 @@ async def make_assertion(_, data, *args, **kwargs):
                     test_flag = data.get("voice") == expected
                 else:
                     test_flag = isinstance(data.get("voice"), InputFile)
+                return dummy_message_dict
 
             monkeypatch.setattr(offline_bot, "_post", make_assertion)
             await offline_bot.send_voice(chat_id, file)
diff --git a/tests/_games/test_gamehighscore.py b/tests/_games/test_gamehighscore.py
index cd84900dbc5..38398816edb 100644
--- a/tests/_games/test_gamehighscore.py
+++ b/tests/_games/test_gamehighscore.py
@@ -55,8 +55,6 @@ def test_de_json(self, offline_bot):
         assert highscore.user == self.user
         assert highscore.score == self.score
 
-        assert GameHighScore.de_json(None, offline_bot) is None
-
     def test_to_dict(self, game_highscore):
         game_highscore_dict = game_highscore.to_dict()
 
diff --git a/tests/_inline/test_inlinekeyboardbutton.py b/tests/_inline/test_inlinekeyboardbutton.py
index 8c2c98a4684..cb7ae3f2a13 100644
--- a/tests/_inline/test_inlinekeyboardbutton.py
+++ b/tests/_inline/test_inlinekeyboardbutton.py
@@ -159,9 +159,6 @@ def test_de_json(self, offline_bot):
         )
         assert inline_keyboard_button.copy_text == self.copy_text
 
-        none = InlineKeyboardButton.de_json({}, offline_bot)
-        assert none is None
-
     def test_equality(self):
         a = InlineKeyboardButton("text", callback_data="data")
         b = InlineKeyboardButton("text", callback_data="data")
diff --git a/tests/_inline/test_inputinvoicemessagecontent.py b/tests/_inline/test_inputinvoicemessagecontent.py
index 88927d18138..a3d6e1a0dc7 100644
--- a/tests/_inline/test_inputinvoicemessagecontent.py
+++ b/tests/_inline/test_inputinvoicemessagecontent.py
@@ -204,7 +204,6 @@ def test_to_dict(self, input_invoice_message_content):
         )
 
     def test_de_json(self, offline_bot):
-        assert InputInvoiceMessageContent.de_json({}, bot=offline_bot) is None
 
         json_dict = {
             "title": self.title,
diff --git a/tests/_payment/stars/test_affiliateinfo.py b/tests/_payment/stars/test_affiliateinfo.py
index 7a21c2cf95b..cfeaeeef514 100644
--- a/tests/_payment/stars/test_affiliateinfo.py
+++ b/tests/_payment/stars/test_affiliateinfo.py
@@ -64,9 +64,6 @@ def test_de_json(self, offline_bot):
         assert ai.amount == self.amount
         assert ai.nanostar_amount == self.nanostar_amount
 
-        assert AffiliateInfo.de_json(None, offline_bot) is None
-        assert AffiliateInfo.de_json({}, offline_bot) is None
-
     def test_to_dict(self, affiliate_info):
         ai_dict = affiliate_info.to_dict()
 
diff --git a/tests/_payment/stars/test_revenuewithdrawelstate.py b/tests/_payment/stars/test_revenuewithdrawelstate.py
index c5265be96c2..55923868a55 100644
--- a/tests/_payment/stars/test_revenuewithdrawelstate.py
+++ b/tests/_payment/stars/test_revenuewithdrawelstate.py
@@ -77,9 +77,6 @@ def test_de_json(self, offline_bot):
         assert rws.api_kwargs == {}
         assert rws.type == "unknown"
 
-        assert RevenueWithdrawalState.de_json(None, offline_bot) is None
-        assert RevenueWithdrawalState.de_json({}, offline_bot) is None
-
     @pytest.mark.parametrize(
         ("state", "subclass"),
         [
@@ -129,8 +126,6 @@ def test_de_json(self, offline_bot):
         assert rws.api_kwargs == {}
         assert rws.type == "pending"
 
-        assert RevenueWithdrawalStatePending.de_json(None, offline_bot) is None
-
     def test_to_dict(self, revenue_withdrawal_state_pending):
         json_dict = revenue_withdrawal_state_pending.to_dict()
         assert json_dict == {"type": "pending"}
@@ -168,8 +163,6 @@ def test_de_json(self, offline_bot):
         assert rws.date == self.date
         assert rws.url == self.url
 
-        assert RevenueWithdrawalStateSucceeded.de_json(None, offline_bot) is None
-
     def test_to_dict(self, revenue_withdrawal_state_succeeded):
         json_dict = revenue_withdrawal_state_succeeded.to_dict()
         assert json_dict["type"] == "succeeded"
@@ -213,8 +206,6 @@ def test_de_json(self, offline_bot):
         assert rws.api_kwargs == {}
         assert rws.type == "failed"
 
-        assert RevenueWithdrawalStateFailed.de_json(None, offline_bot) is None
-
     def test_to_dict(self, revenue_withdrawal_state_failed):
         json_dict = revenue_withdrawal_state_failed.to_dict()
         assert json_dict == {"type": "failed"}
diff --git a/tests/_payment/stars/test_startransactions.py b/tests/_payment/stars/test_startransactions.py
index 4d6553b508f..0878e8cbede 100644
--- a/tests/_payment/stars/test_startransactions.py
+++ b/tests/_payment/stars/test_startransactions.py
@@ -89,7 +89,6 @@ def test_de_json(self, offline_bot):
             "receiver": self.receiver.to_dict(),
         }
         st = StarTransaction.de_json(json_dict, offline_bot)
-        st_none = StarTransaction.de_json(None, offline_bot)
         assert st.api_kwargs == {}
         assert st.id == self.id
         assert st.amount == self.amount
@@ -97,7 +96,6 @@ def test_de_json(self, offline_bot):
         assert st.date == from_timestamp(self.date)
         assert st.source == self.source
         assert st.receiver == self.receiver
-        assert st_none is None
 
     def test_de_json_star_transaction_localization(
         self, tz_bot, offline_bot, raw_bot, star_transaction
@@ -178,10 +176,8 @@ def test_de_json(self, offline_bot):
             "transactions": [t.to_dict() for t in self.transactions],
         }
         st = StarTransactions.de_json(json_dict, offline_bot)
-        st_none = StarTransactions.de_json(None, offline_bot)
         assert st.api_kwargs == {}
         assert st.transactions == tuple(self.transactions)
-        assert st_none is None
 
     def test_to_dict(self, star_transactions):
         expected_dict = {
diff --git a/tests/_payment/stars/test_transactionpartner.py b/tests/_payment/stars/test_transactionpartner.py
index 99cfe383377..02db851e6b8 100644
--- a/tests/_payment/stars/test_transactionpartner.py
+++ b/tests/_payment/stars/test_transactionpartner.py
@@ -114,9 +114,6 @@ def test_de_json(self, offline_bot):
         assert transaction_partner.api_kwargs == {}
         assert transaction_partner.type == "unknown"
 
-        assert TransactionPartner.de_json(None, offline_bot) is None
-        assert TransactionPartner.de_json({}, offline_bot) is None
-
     @pytest.mark.parametrize(
         ("tp_type", "subclass"),
         [
@@ -191,9 +188,6 @@ def test_de_json(self, offline_bot):
         assert tp.commission_per_mille == self.commission_per_mille
         assert tp.sponsor_user == self.sponsor_user
 
-        assert TransactionPartnerAffiliateProgram.de_json(None, offline_bot) is None
-        assert TransactionPartnerAffiliateProgram.de_json({}, offline_bot) is None
-
     def test_to_dict(self, transaction_partner_affiliate_program):
         json_dict = transaction_partner_affiliate_program.to_dict()
         assert json_dict["type"] == self.type
@@ -243,8 +237,6 @@ def test_de_json(self, offline_bot):
         assert tp.type == "fragment"
         assert tp.withdrawal_state == self.withdrawal_state
 
-        assert TransactionPartnerFragment.de_json(None, offline_bot) is None
-
     def test_to_dict(self, transaction_partner_fragment):
         json_dict = transaction_partner_fragment.to_dict()
         assert json_dict["type"] == self.type
@@ -303,9 +295,6 @@ def test_de_json(self, offline_bot):
         assert tp.paid_media_payload == self.paid_media_payload
         assert tp.subscription_period == self.subscription_period
 
-        assert TransactionPartnerUser.de_json(None, offline_bot) is None
-        assert TransactionPartnerUser.de_json({}, offline_bot) is None
-
     def test_to_dict(self, transaction_partner_user):
         json_dict = transaction_partner_user.to_dict()
         assert json_dict["type"] == self.type
@@ -355,8 +344,6 @@ def test_de_json(self, offline_bot):
         assert tp.api_kwargs == {}
         assert tp.type == "other"
 
-        assert TransactionPartnerOther.de_json(None, offline_bot) is None
-
     def test_to_dict(self, transaction_partner_other):
         json_dict = transaction_partner_other.to_dict()
         assert json_dict == {"type": self.type}
@@ -397,8 +384,6 @@ def test_de_json(self, offline_bot):
         assert tp.api_kwargs == {}
         assert tp.type == "telegram_ads"
 
-        assert TransactionPartnerTelegramAds.de_json(None, offline_bot) is None
-
     def test_to_dict(self, transaction_partner_telegram_ads):
         json_dict = transaction_partner_telegram_ads.to_dict()
         assert json_dict == {"type": self.type}
@@ -442,8 +427,6 @@ def test_de_json(self, offline_bot):
         assert tp.type == "telegram_api"
         assert tp.request_count == self.request_count
 
-        assert TransactionPartnerTelegramApi.de_json(None, offline_bot) is None
-
     def test_to_dict(self, transaction_partner_telegram_api):
         json_dict = transaction_partner_telegram_api.to_dict()
         assert json_dict["type"] == self.type
diff --git a/tests/auxil/bot_method_checks.py b/tests/auxil/bot_method_checks.py
index 7e50a8dae85..8e3179ea944 100644
--- a/tests/auxil/bot_method_checks.py
+++ b/tests/auxil/bot_method_checks.py
@@ -23,7 +23,8 @@
 import re
 import zoneinfo
 from collections.abc import Collection, Iterable
-from typing import Any, Callable, Optional
+from types import GenericAlias
+from typing import Any, Callable, ForwardRef, Optional, Union
 
 import pytest
 
@@ -31,7 +32,6 @@
 from telegram import (
     Bot,
     ChatPermissions,
-    File,
     InlineQueryResultArticle,
     InlineQueryResultCachedPhoto,
     InputMediaPhoto,
@@ -46,6 +46,7 @@
 from telegram.constants import InputMediaType
 from telegram.ext import Defaults, ExtBot
 from telegram.request import RequestData
+from tests.auxil.dummy_objects import get_dummy_object_json_dict
 
 FORWARD_REF_PATTERN = re.compile(r"ForwardRef\('(?P<class_name>\w+)'\)")
 """ A pattern to find a class name in a ForwardRef typing annotation.
@@ -258,10 +259,6 @@ async def make_assertion(**kw):
                 f"{expected_args - received_kwargs}"
             )
 
-        if bot_method_name == "get_file":
-            # This is here mainly for PassportFile.get_file, which calls .set_credentials on the
-            # return value
-            return File(file_id="result", file_unique_id="result")
         return True
 
     setattr(bot, bot_method_name, make_assertion)
@@ -392,6 +389,33 @@ def make_assertion_for_link_preview_options(
             )
 
 
+def _check_forward_ref(obj: object) -> Union[str, object]:
+    if isinstance(obj, ForwardRef):
+        return obj.__forward_arg__
+    return obj
+
+
+def guess_return_type_name(method: Callable[[...], Any]) -> tuple[Union[str, object], bool]:
+    # Using typing.get_type_hints(method) would be the nicer as it also resolves ForwardRefs
+    # and string annotations. But it also wants to resolve the parameter annotations, which
+    # need additional namespaces and that's not worth the struggle for now …
+    return_annotation = _check_forward_ref(inspect.signature(method).return_annotation)
+    as_tuple = False
+
+    if isinstance(return_annotation, GenericAlias):
+        if return_annotation.__origin__ is tuple:
+            as_tuple = True
+        else:
+            raise ValueError(
+                f"Return type of {method.__name__} is a GenericAlias. This can not be handled yet."
+            )
+
+    # For tuples and Unions, we simply take the first element
+    if hasattr(return_annotation, "__args__"):
+        return _check_forward_ref(return_annotation.__args__[0]), as_tuple
+    return return_annotation, as_tuple
+
+
 _EUROPE_BERLIN_TS = to_timestamp(
     dtm.datetime(2000, 1, 1, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"))
 )
@@ -547,15 +571,6 @@ def check_input_media(m: dict):
             if default_value_expected and date_param != _AMERICA_NEW_YORK_TS:
                 pytest.fail(f"Naive `{key}` should have been interpreted as America/New_York")
 
-    if method_name in ["get_file", "get_small_file", "get_big_file"]:
-        # This is here mainly for PassportFile.get_file, which calls .set_credentials on the
-        # return value
-        out = File(file_id="result", file_unique_id="result")
-        return out.to_dict()
-    # Otherwise return None by default, as TGObject.de_json/list(None) in [None, []]
-    # That way we can check what gets passed to Request.post without having to actually
-    # make a request
-    # Some methods expect specific output, so we allow to customize that
     if isinstance(return_value, TelegramObject):
         return return_value.to_dict()
     return return_value
@@ -564,7 +579,6 @@ def check_input_media(m: dict):
 async def check_defaults_handling(
     method: Callable,
     bot: Bot,
-    return_value=None,
     no_default_kwargs: Collection[str] = frozenset(),
 ) -> bool:
     """
@@ -574,9 +588,6 @@ async def check_defaults_handling(
         method: The shortcut/bot_method
         bot: The bot. May be a telegram.Bot or a telegram.ext.ExtBot. In the former case, all
             default values will be converted to None.
-        return_value: Optional. The return value of Bot._post that the method expects. Defaults to
-            None. get_file is automatically handled. If this is a `TelegramObject`, Bot._post will
-            return the `to_dict` representation of it.
         no_default_kwargs: Optional. A collection of keyword arguments that should not have default
             values. Defaults to an empty frozenset.
 
@@ -612,12 +623,10 @@ async def check_defaults_handling(
     )
     defaults_custom_defaults = Defaults(**kwargs)
 
-    expected_return_values = [None, ()] if return_value is None else [return_value]
-    if method.__name__ in ["get_file", "get_small_file", "get_big_file"]:
-        expected_return_values = [File(file_id="result", file_unique_id="result")]
-
     request = bot._request[0] if get_updates else bot.request
     orig_post = request.post
+    return_value = get_dummy_object_json_dict(*guess_return_type_name(method))
+
     try:
         if raw_bot:
             combinations = [(None, None)]
@@ -641,7 +650,7 @@ async def check_defaults_handling(
                 expected_defaults_value=expected_defaults_value,
             )
             request.post = assertion_callback
-            assert await method(**kwargs) in expected_return_values
+            await method(**kwargs)
 
             # 2: test that we get the manually passed non-None value
             kwargs = build_kwargs(
@@ -656,7 +665,7 @@ async def check_defaults_handling(
                 expected_defaults_value=expected_defaults_value,
             )
             request.post = assertion_callback
-            assert await method(**kwargs) in expected_return_values
+            await method(**kwargs)
 
             # 3: test that we get the manually passed None value
             kwargs = build_kwargs(
@@ -671,7 +680,7 @@ async def check_defaults_handling(
                 expected_defaults_value=expected_defaults_value,
             )
             request.post = assertion_callback
-            assert await method(**kwargs) in expected_return_values
+            await method(**kwargs)
     except Exception as exc:
         raise exc
     finally:
diff --git a/tests/auxil/dummy_objects.py b/tests/auxil/dummy_objects.py
new file mode 100644
index 00000000000..7e504f0db78
--- /dev/null
+++ b/tests/auxil/dummy_objects.py
@@ -0,0 +1,166 @@
+import datetime as dtm
+from collections.abc import Sequence
+from typing import Union
+
+from telegram import (
+    BotCommand,
+    BotDescription,
+    BotName,
+    BotShortDescription,
+    BusinessConnection,
+    Chat,
+    ChatAdministratorRights,
+    ChatBoost,
+    ChatBoostSource,
+    ChatFullInfo,
+    ChatInviteLink,
+    ChatMember,
+    File,
+    ForumTopic,
+    GameHighScore,
+    Gift,
+    Gifts,
+    MenuButton,
+    MessageId,
+    Poll,
+    PollOption,
+    PreparedInlineMessage,
+    SentWebAppMessage,
+    StarTransaction,
+    StarTransactions,
+    Sticker,
+    StickerSet,
+    TelegramObject,
+    Update,
+    User,
+    UserChatBoosts,
+    UserProfilePhotos,
+    WebhookInfo,
+)
+from tests.auxil.build_messages import make_message
+
+_DUMMY_USER = User(
+    id=123456, is_bot=False, first_name="Dummy", last_name="User", username="dummy_user"
+)
+_DUMMY_DATE = dtm.datetime(1970, 1, 1, 0, 0, 0, 0, tzinfo=dtm.timezone.utc)
+_DUMMY_STICKER = Sticker(
+    file_id="dummy_file_id",
+    file_unique_id="dummy_file_unique_id",
+    width=1,
+    height=1,
+    is_animated=False,
+    is_video=False,
+    type="dummy_type",
+)
+
+_PREPARED_DUMMY_OBJECTS: dict[str, object] = {
+    "bool": True,
+    "BotCommand": BotCommand(command="dummy_command", description="dummy_description"),
+    "BotDescription": BotDescription(description="dummy_description"),
+    "BotName": BotName(name="dummy_name"),
+    "BotShortDescription": BotShortDescription(short_description="dummy_short_description"),
+    "BusinessConnection": BusinessConnection(
+        user=_DUMMY_USER,
+        id="123",
+        user_chat_id=123456,
+        date=_DUMMY_DATE,
+        can_reply=True,
+        is_enabled=True,
+    ),
+    "Chat": Chat(id=123456, type="dummy_type"),
+    "ChatAdministratorRights": ChatAdministratorRights.all_rights(),
+    "ChatFullInfo": ChatFullInfo(
+        id=123456,
+        type="dummy_type",
+        accent_color_id=1,
+        max_reaction_count=1,
+    ),
+    "ChatInviteLink": ChatInviteLink(
+        "dummy_invite_link",
+        creator=_DUMMY_USER,
+        is_primary=True,
+        is_revoked=False,
+        creates_join_request=False,
+    ),
+    "ChatMember": ChatMember(user=_DUMMY_USER, status="dummy_status"),
+    "File": File(file_id="dummy_file_id", file_unique_id="dummy_file_unique_id"),
+    "ForumTopic": ForumTopic(message_thread_id=2, name="dummy_name", icon_color=1),
+    "Gifts": Gifts(gifts=[Gift(id="dummy_id", sticker=_DUMMY_STICKER, star_count=1)]),
+    "GameHighScore": GameHighScore(position=1, user=_DUMMY_USER, score=1),
+    "int": 123456,
+    "MenuButton": MenuButton(type="dummy_type"),
+    "Message": make_message("dummy_text"),
+    "MessageId": MessageId(123456),
+    "Poll": Poll(
+        id="dummy_id",
+        question="dummy_question",
+        options=[PollOption(text="dummy_text", voter_count=1)],
+        is_closed=False,
+        is_anonymous=False,
+        total_voter_count=1,
+        type="dummy_type",
+        allows_multiple_answers=False,
+    ),
+    "PreparedInlineMessage": PreparedInlineMessage(id="dummy_id", expiration_date=_DUMMY_DATE),
+    "SentWebAppMessage": SentWebAppMessage(inline_message_id="dummy_inline_message_id"),
+    "StarTransactions": StarTransactions(
+        transactions=[StarTransaction(id="dummy_id", amount=1, date=_DUMMY_DATE)]
+    ),
+    "Sticker": _DUMMY_STICKER,
+    "StickerSet": StickerSet(
+        name="dummy_name",
+        title="dummy_title",
+        stickers=[_DUMMY_STICKER],
+        sticker_type="dummy_type",
+    ),
+    "str": "dummy_string",
+    "Update": Update(update_id=123456),
+    "User": _DUMMY_USER,
+    "UserChatBoosts": UserChatBoosts(
+        boosts=[
+            ChatBoost(
+                boost_id="dummy_id",
+                add_date=_DUMMY_DATE,
+                expiration_date=_DUMMY_DATE,
+                source=ChatBoostSource(source="dummy_source"),
+            )
+        ]
+    ),
+    "UserProfilePhotos": UserProfilePhotos(total_count=1, photos=[[]]),
+    "WebhookInfo": WebhookInfo(
+        url="dummy_url",
+        has_custom_certificate=False,
+        pending_update_count=1,
+    ),
+}
+
+
+def get_dummy_object(obj_type: Union[type, str], as_tuple: bool = False) -> object:
+    obj_type_name = obj_type.__name__ if isinstance(obj_type, type) else obj_type
+    if (return_value := _PREPARED_DUMMY_OBJECTS.get(obj_type_name)) is None:
+        raise ValueError(
+            f"Dummy object of type '{obj_type_name}' not found. Please add it manually."
+        )
+
+    if as_tuple:
+        return (return_value,)
+    return return_value
+
+
+_RETURN_TYPES = Union[bool, int, str, dict[str, object]]
+_RETURN_TYPE = Union[_RETURN_TYPES, tuple[_RETURN_TYPES, ...]]
+
+
+def _serialize_dummy_object(obj: object) -> _RETURN_TYPE:
+    if isinstance(obj, Sequence) and not isinstance(obj, str):
+        return tuple(_serialize_dummy_object(item) for item in obj)
+    if isinstance(obj, (str, int, bool)):
+        return obj
+    if isinstance(obj, TelegramObject):
+        return obj.to_dict()
+
+    raise ValueError(f"Serialization of object of type '{type(obj)}' is not supported yet.")
+
+
+def get_dummy_object_json_dict(obj_type: Union[type, str], as_tuple: bool = False) -> _RETURN_TYPE:
+    return _serialize_dummy_object(get_dummy_object(obj_type, as_tuple=as_tuple))
diff --git a/tests/auxil/pytest_classes.py b/tests/auxil/pytest_classes.py
index d27f06bd8c5..c3694a8f0aa 100644
--- a/tests/auxil/pytest_classes.py
+++ b/tests/auxil/pytest_classes.py
@@ -66,7 +66,7 @@ def __init__(self, *args, **kwargs):
         self._unfreeze()
 
     # Here we override get_me for caching because we don't want to call the API repeatedly in tests
-    async def get_me(self, *args, **kwargs):
+    async def get_me(self, *args, **kwargs) -> User:
         return await _mocked_get_me(self)
 
 
@@ -77,7 +77,7 @@ def __init__(self, *args, **kwargs):
         self._unfreeze()
 
     # Here we override get_me for caching because we don't want to call the API repeatedly in tests
-    async def get_me(self, *args, **kwargs):
+    async def get_me(self, *args, **kwargs) -> User:
         return await _mocked_get_me(self)
 
 
diff --git a/tests/conftest.py b/tests/conftest.py
index 40e7c34b8bc..935daada498 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -37,7 +37,7 @@
     User,
 )
 from telegram.ext import Defaults
-from tests.auxil.build_messages import DATE
+from tests.auxil.build_messages import DATE, make_message
 from tests.auxil.ci_bots import BOT_INFO_PROVIDER, JOB_INDEX
 from tests.auxil.constants import PRIVATE_KEY, TEST_TOPIC_ICON_COLOR, TEST_TOPIC_NAME
 from tests.auxil.envvars import GITHUB_ACTIONS, RUN_TEST_OFFICIAL, TEST_WITH_OPT_DEPS
@@ -331,3 +331,13 @@ def timezone(tzinfo):
 @pytest.fixture
 def tmp_file(tmp_path) -> Path:
     return tmp_path / uuid4().hex
+
+
+@pytest.fixture(scope="session")
+def dummy_message():
+    return make_message("dummy_message")
+
+
+@pytest.fixture(scope="session")
+def dummy_message_dict(dummy_message):
+    return dummy_message.to_dict()
diff --git a/tests/test_bot.py b/tests/test_bot.py
index 35a9fa017ac..1245b5b8575 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -43,6 +43,7 @@
     Chat,
     ChatAdministratorRights,
     ChatFullInfo,
+    ChatInviteLink,
     ChatPermissions,
     Dice,
     InlineKeyboardButton,
@@ -104,6 +105,7 @@
 from tests.auxil.slots import mro_slots
 
 from .auxil.build_messages import make_message
+from .auxil.dummy_objects import get_dummy_object
 
 
 @pytest.fixture
@@ -487,17 +489,11 @@ async def test_defaults_handling(
         Finally, there are some tests for Defaults.{parse_mode, quote, allow_sending_without_reply}
         at the appropriate places, as those are the only things we can actually check.
         """
-        # Mocking get_me within check_defaults_handling messes with the cached values like
-        # Bot.{bot, username, id, …}` unless we return the expected User object.
-        return_value = (
-            offline_bot.bot if bot_method_name.lower().replace("_", "") == "getme" else None
-        )
-
         # Check that ExtBot does the right thing
         bot_method = getattr(offline_bot, bot_method_name)
         raw_bot_method = getattr(raw_bot, bot_method_name)
-        assert await check_defaults_handling(bot_method, offline_bot, return_value=return_value)
-        assert await check_defaults_handling(raw_bot_method, raw_bot, return_value=return_value)
+        assert await check_defaults_handling(bot_method, offline_bot)
+        assert await check_defaults_handling(raw_bot_method, raw_bot)
 
     @pytest.mark.parametrize(
         ("name", "method"), inspect.getmembers(Bot, predicate=inspect.isfunction)
@@ -1432,7 +1428,9 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
         )
 
     @pytest.mark.parametrize("local_mode", [True, False])
-    async def test_set_chat_photo_local_files(self, monkeypatch, offline_bot, chat_id, local_mode):
+    async def test_set_chat_photo_local_files(
+        self, dummy_message_dict, monkeypatch, offline_bot, chat_id, local_mode
+    ):
         try:
             offline_bot._local_mode = local_mode
             # For just test that the correct paths are passed as we have no local Bot API set up
@@ -1628,7 +1626,7 @@ async def test_arbitrary_callback_data_pinned_message_reply_to_message(
         message = Message(
             1,
             dtm.datetime.utcnow(),
-            None,
+            get_dummy_object(Chat),
             reply_markup=offline_bot.callback_data_cache.process_keyboard(reply_markup),
         )
         message._unfreeze()
@@ -1642,7 +1640,7 @@ async def post(*args, **kwargs):
                     message_type: Message(
                         1,
                         dtm.datetime.utcnow(),
-                        None,
+                        get_dummy_object(Chat),
                         pinned_message=message,
                         reply_to_message=Message.de_json(message.to_dict(), offline_bot),
                     )
@@ -1785,7 +1783,7 @@ async def test_arbitrary_callback_data_via_bot(
         message = Message(
             1,
             dtm.datetime.utcnow(),
-            None,
+            get_dummy_object(Chat),
             reply_markup=reply_markup,
             via_bot=bot.bot if self_sender else User(1, "first", False),
         )
@@ -2228,14 +2226,32 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
             api_kwargs={"chat_id": 2, "user_id": 32, "until_date": until_timestamp},
         )
 
-    async def test_business_connection_id_argument(self, offline_bot, monkeypatch):
+    async def test_business_connection_id_argument(
+        self, offline_bot, monkeypatch, dummy_message_dict
+    ):
         """We can't connect to a business acc, so we just test that the correct data is passed.
         We also can't test every single method easily, so we just test a few. Our linting will
         catch any unused args with the others."""
+        return_values = asyncio.Queue()
+        await return_values.put(dummy_message_dict)
+        await return_values.put(
+            Poll(
+                id="42",
+                question="question",
+                options=[PollOption("option", 0)],
+                total_voter_count=5,
+                is_closed=True,
+                is_anonymous=True,
+                type="regular",
+                allows_multiple_answers=False,
+            ).to_dict()
+        )
+        await return_values.put(True)
+        await return_values.put(True)
 
         async def make_assertion(url, request_data: RequestData, *args, **kwargs):
             assert request_data.parameters.get("business_connection_id") == 42
-            return {}
+            return await return_values.get()
 
         monkeypatch.setattr(offline_bot.request, "post", make_assertion)
 
@@ -2348,6 +2364,9 @@ async def test_create_chat_subscription_invite_link(
         async def make_assertion(url, request_data: RequestData, *args, **kwargs):
             assert request_data.parameters.get("subscription_period") == 2592000
             assert request_data.parameters.get("subscription_price") == 6
+            return ChatInviteLink(
+                "https://t.me/joinchat/invite_link", User(1, "first", False), False, False, False
+            ).to_dict()
 
         monkeypatch.setattr(offline_bot.request, "post", make_assertion)
 
diff --git a/tests/test_botcommand.py b/tests/test_botcommand.py
index 7dd2070c098..00fa63ed0d2 100644
--- a/tests/test_botcommand.py
+++ b/tests/test_botcommand.py
@@ -45,8 +45,6 @@ def test_de_json(self, offline_bot):
         assert bot_command.command == self.command
         assert bot_command.description == self.description
 
-        assert BotCommand.de_json(None, offline_bot) is None
-
     def test_to_dict(self, bot_command):
         bot_command_dict = bot_command.to_dict()
 
diff --git a/tests/test_botcommandscope.py b/tests/test_botcommandscope.py
index 2acafaeb93b..e1ae9f585c4 100644
--- a/tests/test_botcommandscope.py
+++ b/tests/test_botcommandscope.py
@@ -129,8 +129,6 @@ def test_de_json(self, offline_bot, scope_class_and_type, chat_id):
         cls = scope_class_and_type[0]
         type_ = scope_class_and_type[1]
 
-        assert cls.de_json({}, offline_bot) is None
-
         json_dict = {"type": type_, "chat_id": chat_id, "user_id": 42}
         bot_command_scope = BotCommandScope.de_json(json_dict, offline_bot)
         assert set(bot_command_scope.api_kwargs.keys()) == {"chat_id", "user_id"} - set(
diff --git a/tests/test_chatbackground.py b/tests/test_chatbackground.py
index 33e257d3e83..f1dd40b9325 100644
--- a/tests/test_chatbackground.py
+++ b/tests/test_chatbackground.py
@@ -170,7 +170,6 @@ def test_slot_behaviour(self, background_type):
 
     def test_de_json_required_args(self, offline_bot, background_type):
         cls = background_type.__class__
-        assert cls.de_json({}, offline_bot) is None
 
         json_dict = make_json_dict(background_type)
         const_background_type = BackgroundType.de_json(json_dict, offline_bot)
@@ -277,7 +276,6 @@ def test_slot_behaviour(self, background_fill):
 
     def test_de_json_required_args(self, offline_bot, background_fill):
         cls = background_fill.__class__
-        assert cls.de_json({}, offline_bot) is None
 
         json_dict = make_json_dict(background_fill)
         const_background_fill = BackgroundFill.de_json(json_dict, offline_bot)
diff --git a/tests/test_chatboost.py b/tests/test_chatboost.py
index 60692289b83..0440a0ff44c 100644
--- a/tests/test_chatboost.py
+++ b/tests/test_chatboost.py
@@ -38,6 +38,7 @@
 from telegram._utils.datetime import UTC, to_timestamp
 from telegram.constants import ChatBoostSources
 from telegram.request import RequestData
+from tests.auxil.dummy_objects import get_dummy_object_json_dict
 from tests.auxil.slots import mro_slots
 
 
@@ -174,8 +175,6 @@ def test_slot_behaviour(self, chat_boost_source):
 
     def test_de_json_required_args(self, offline_bot, chat_boost_source):
         cls = chat_boost_source.__class__
-        assert cls.de_json({}, offline_bot) is None
-        assert ChatBoost.de_json({}, offline_bot) is None
 
         json_dict = make_json_dict(chat_boost_source)
         const_boost_source = ChatBoostSource.de_json(json_dict, offline_bot)
@@ -534,7 +533,7 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs):
             user_id = data["user_id"] == "2"
             if not all((chat_id, user_id)):
                 pytest.fail("I got wrong parameters in post")
-            return data
+            return get_dummy_object_json_dict(UserChatBoosts)
 
         monkeypatch.setattr(offline_bot.request, "post", make_assertion)
 
diff --git a/tests/test_chatmember.py b/tests/test_chatmember.py
index e4f6da387ac..359e0727878 100644
--- a/tests/test_chatmember.py
+++ b/tests/test_chatmember.py
@@ -205,7 +205,6 @@ def test_slot_behaviour(self, chat_member_type):
 
     def test_de_json_required_args(self, offline_bot, chat_member_type):
         cls = chat_member_type.__class__
-        assert cls.de_json({}, offline_bot) is None
 
         json_dict = make_json_dict(chat_member_type)
         const_chat_member = ChatMember.de_json(json_dict, offline_bot)
diff --git a/tests/test_copytextbutton.py b/tests/test_copytextbutton.py
index c571b485b4c..398a4bf5401 100644
--- a/tests/test_copytextbutton.py
+++ b/tests/test_copytextbutton.py
@@ -46,7 +46,6 @@ def test_de_json(self, offline_bot):
         assert copy_text_button.api_kwargs == {}
 
         assert copy_text_button.text == self.text
-        assert CopyTextButton.de_json(None, offline_bot) is None
 
     def test_to_dict(self, copy_text_button):
         copy_text_button_dict = copy_text_button.to_dict()
diff --git a/tests/test_dice.py b/tests/test_dice.py
index e0f5f89c972..707d4bf1eeb 100644
--- a/tests/test_dice.py
+++ b/tests/test_dice.py
@@ -46,7 +46,6 @@ def test_de_json(self, offline_bot, emoji):
 
         assert dice.value == self.value
         assert dice.emoji == emoji
-        assert Dice.de_json(None, offline_bot) is None
 
     def test_to_dict(self, dice):
         dice_dict = dice.to_dict()
diff --git a/tests/test_forum.py b/tests/test_forum.py
index d5c6b1a5ada..11bec6ea2f2 100644
--- a/tests/test_forum.py
+++ b/tests/test_forum.py
@@ -60,7 +60,6 @@ async def test_expected_values(self, emoji_id, forum_group_id, forum_topic_objec
         assert forum_topic_object.icon_custom_emoji_id == emoji_id
 
     def test_de_json(self, offline_bot, emoji_id, forum_group_id):
-        assert ForumTopic.de_json(None, bot=offline_bot) is None
 
         json_dict = {
             "message_thread_id": forum_group_id,
@@ -307,7 +306,6 @@ def test_expected_values(self, topic_created):
         assert topic_created.name == TEST_TOPIC_NAME
 
     def test_de_json(self, offline_bot):
-        assert ForumTopicCreated.de_json(None, bot=offline_bot) is None
 
         json_dict = {"icon_color": TEST_TOPIC_ICON_COLOR, "name": TEST_TOPIC_NAME}
         action = ForumTopicCreated.de_json(json_dict, offline_bot)
@@ -395,8 +393,6 @@ def test_expected_values(self, topic_edited, emoji_id):
         assert topic_edited.icon_custom_emoji_id == emoji_id
 
     def test_de_json(self, bot, emoji_id):
-        assert ForumTopicEdited.de_json(None, bot=bot) is None
-
         json_dict = {"name": TEST_TOPIC_NAME, "icon_custom_emoji_id": emoji_id}
         action = ForumTopicEdited.de_json(json_dict, bot)
         assert action.api_kwargs == {}
diff --git a/tests/test_gifts.py b/tests/test_gifts.py
index d294aa8dba9..f350af95991 100644
--- a/tests/test_gifts.py
+++ b/tests/test_gifts.py
@@ -80,8 +80,6 @@ def test_de_json(self, offline_bot, gift):
         assert gift.remaining_count == self.remaining_count
         assert gift.upgrade_star_count == self.upgrade_star_count
 
-        assert Gift.de_json(None, offline_bot) is None
-
     def test_to_dict(self, gift):
         gift_dict = gift.to_dict()
 
@@ -266,8 +264,6 @@ def test_de_json(self, offline_bot, gifts):
             assert de_json_gift.remaining_count == original_gift.remaining_count
             assert de_json_gift.upgrade_star_count == original_gift.upgrade_star_count
 
-        assert Gifts.de_json(None, offline_bot) is None
-
     def test_to_dict(self, gifts):
         gifts_dict = gifts.to_dict()
 
diff --git a/tests/test_giveaway.py b/tests/test_giveaway.py
index 8ec07e59ee9..bf186002ce2 100644
--- a/tests/test_giveaway.py
+++ b/tests/test_giveaway.py
@@ -94,8 +94,6 @@ def test_de_json(self, offline_bot):
         assert giveaway.premium_subscription_month_count == self.premium_subscription_month_count
         assert giveaway.prize_star_count == self.prize_star_count
 
-        assert Giveaway.de_json(None, offline_bot) is None
-
     def test_de_json_localization(self, tz_bot, offline_bot, raw_bot):
         json_dict = {
             "chats": [chat.to_dict() for chat in self.chats],
@@ -196,8 +194,6 @@ def test_de_json(self, bot):
         assert gac.api_kwargs == {}
         assert gac.prize_star_count == self.prize_star_count
 
-        assert Giveaway.de_json(None, bot) is None
-
     def test_to_dict(self, giveaway_created):
         gac_dict = giveaway_created.to_dict()
 
@@ -281,8 +277,6 @@ def test_de_json(self, offline_bot):
         assert giveaway_winners.prize_description == self.prize_description
         assert giveaway_winners.prize_star_count == self.prize_star_count
 
-        assert GiveawayWinners.de_json(None, offline_bot) is None
-
     def test_de_json_localization(self, tz_bot, offline_bot, raw_bot):
         json_dict = {
             "chat": self.chat.to_dict(),
@@ -411,8 +405,6 @@ def test_de_json(self, offline_bot):
         assert giveaway_completed.giveaway_message == self.giveaway_message
         assert giveaway_completed.is_star_giveaway == self.is_star_giveaway
 
-        assert GiveawayCompleted.de_json(None, offline_bot) is None
-
     def test_to_dict(self, giveaway_completed):
         giveaway_completed_dict = giveaway_completed.to_dict()
 
diff --git a/tests/test_inlinequeryresultsbutton.py b/tests/test_inlinequeryresultsbutton.py
index 192fdc2904d..34f3e267d6e 100644
--- a/tests/test_inlinequeryresultsbutton.py
+++ b/tests/test_inlinequeryresultsbutton.py
@@ -52,8 +52,6 @@ def test_to_dict(self, inline_query_results_button):
         assert inline_query_results_button_dict["web_app"] == self.web_app.to_dict()
 
     def test_de_json(self, offline_bot):
-        assert InlineQueryResultsButton.de_json(None, offline_bot) is None
-        assert InlineQueryResultsButton.de_json({}, offline_bot) is None
 
         json_dict = {
             "text": self.text,
diff --git a/tests/test_keyboardbutton.py b/tests/test_keyboardbutton.py
index 35db28b3924..ea55920d2b2 100644
--- a/tests/test_keyboardbutton.py
+++ b/tests/test_keyboardbutton.py
@@ -108,9 +108,6 @@ def test_de_json(self, request_user):
         assert keyboard_button.request_chat == self.request_chat
         assert keyboard_button.request_users == self.request_users
 
-        none = KeyboardButton.de_json({}, None)
-        assert none is None
-
     def test_equality(self):
         a = KeyboardButton("test", request_contact=True)
         b = KeyboardButton("test", request_contact=True)
diff --git a/tests/test_keyboardbuttonrequest.py b/tests/test_keyboardbuttonrequest.py
index f196977d309..93c5ef5d921 100644
--- a/tests/test_keyboardbuttonrequest.py
+++ b/tests/test_keyboardbuttonrequest.py
@@ -179,9 +179,6 @@ def test_de_json(self, offline_bot):
         assert request_chat.bot_administrator_rights == self.bot_administrator_rights
         assert request_chat.bot_is_member == self.bot_is_member
 
-        empty_chat = KeyboardButtonRequestChat.de_json({}, offline_bot)
-        assert empty_chat is None
-
     def test_equality(self):
         a = KeyboardButtonRequestChat(self.request_id, True)
         b = KeyboardButtonRequestChat(self.request_id, True)
diff --git a/tests/test_menubutton.py b/tests/test_menubutton.py
index ef9afebff16..ac03f309671 100644
--- a/tests/test_menubutton.py
+++ b/tests/test_menubutton.py
@@ -119,9 +119,6 @@ def test_de_json(self, offline_bot, scope_class_and_type):
         if "text" in cls.__slots__:
             assert menu_button.text == self.text
 
-        assert cls.de_json(None, offline_bot) is None
-        assert MenuButton.de_json({}, offline_bot) is None
-
     def test_de_json_invalid_type(self, offline_bot):
         json_dict = {"type": "invalid", "text": self.text, "web_app": self.web_app.to_dict()}
         menu_button = MenuButton.de_json(json_dict, offline_bot)
diff --git a/tests/test_message.py b/tests/test_message.py
index 5ef3ba1e50c..525dbaad07a 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -89,6 +89,7 @@
     check_shortcut_signature,
 )
 from tests.auxil.build_messages import make_message
+from tests.auxil.dummy_objects import get_dummy_object_json_dict
 from tests.auxil.pytest_classes import PytestExtBot, PytestMessage
 from tests.auxil.slots import mro_slots
 
@@ -591,9 +592,9 @@ def test_all_possibilities_de_json_and_to_dict(self, offline_bot, message_params
     def test_de_json_localization(self, offline_bot, raw_bot, tz_bot):
         json_dict = {
             "message_id": 12,
-            "from_user": None,
+            "from_user": get_dummy_object_json_dict("User"),
             "date": int(dtm.datetime.now().timestamp()),
-            "chat": None,
+            "chat": get_dummy_object_json_dict("Chat"),
             "edit_date": int(dtm.datetime.now().timestamp()),
         }
 
diff --git a/tests/test_messageorigin.py b/tests/test_messageorigin.py
index 14eec28ebd9..12e3d9fdbc3 100644
--- a/tests/test_messageorigin.py
+++ b/tests/test_messageorigin.py
@@ -138,7 +138,6 @@ def test_slot_behaviour(self, message_origin_type):
 
     def test_de_json_required_args(self, offline_bot, message_origin_type):
         cls = message_origin_type.__class__
-        assert cls.de_json({}, offline_bot) is None
 
         json_dict = make_json_dict(message_origin_type)
         const_message_origin = MessageOrigin.de_json(json_dict, offline_bot)
diff --git a/tests/test_paidmedia.py b/tests/test_paidmedia.py
index e99c0d0e903..e6c22959dc0 100644
--- a/tests/test_paidmedia.py
+++ b/tests/test_paidmedia.py
@@ -193,9 +193,6 @@ def test_de_json(self, offline_bot, pm_scope_class_and_type):
         if "photo" in cls.__slots__:
             assert pm.photo == self.photo
 
-        assert cls.de_json(None, offline_bot) is None
-        assert PaidMedia.de_json({}, offline_bot) is None
-
     def test_de_json_invalid_type(self, offline_bot):
         json_dict = {
             "type": "invalid",
@@ -308,10 +305,8 @@ def test_de_json(self, offline_bot):
             "paid_media": [t.to_dict() for t in self.paid_media],
         }
         pmi = PaidMediaInfo.de_json(json_dict, offline_bot)
-        pmi_none = PaidMediaInfo.de_json(None, offline_bot)
         assert pmi.paid_media == tuple(self.paid_media)
         assert pmi.star_count == self.star_count
-        assert pmi_none is None
 
     def test_to_dict(self, paid_media_info):
         assert paid_media_info.to_dict() == {
@@ -353,11 +348,9 @@ def test_de_json(self, bot):
             "paid_media_payload": self.paid_media_payload,
         }
         pmp = PaidMediaPurchased.de_json(json_dict, bot)
-        pmp_none = PaidMediaPurchased.de_json(None, bot)
         assert pmp.from_user == self.from_user
         assert pmp.paid_media_payload == self.paid_media_payload
         assert pmp.api_kwargs == {}
-        assert pmp_none is None
 
     def test_to_dict(self, paid_media_purchased):
         assert paid_media_purchased.to_dict() == {
diff --git a/tests/test_poll.py b/tests/test_poll.py
index 42c44c6fb58..c7e3da447f5 100644
--- a/tests/test_poll.py
+++ b/tests/test_poll.py
@@ -54,7 +54,6 @@ def test_slot_behaviour(self, input_poll_option):
         ), "duplicate slot"
 
     def test_de_json(self):
-        assert InputPollOption.de_json({}, None) is None
 
         json_dict = {
             "text": self.text,
@@ -144,7 +143,7 @@ def test_de_json_all(self):
             "text_entities": [e.to_dict() for e in self.text_entities],
         }
         poll_option = PollOption.de_json(json_dict, None)
-        assert PollOption.de_json(None, None) is None
+
         assert poll_option.api_kwargs == {}
 
         assert poll_option.text == self.text
diff --git a/tests/test_reaction.py b/tests/test_reaction.py
index 84a37af94a7..8c209500ed2 100644
--- a/tests/test_reaction.py
+++ b/tests/test_reaction.py
@@ -117,8 +117,6 @@ def test_slot_behaviour(self, reaction_type):
 
     def test_de_json_required_args(self, offline_bot, reaction_type):
         cls = reaction_type.__class__
-        assert cls.de_json(None, offline_bot) is None
-        assert ReactionType.de_json({}, offline_bot) is None
 
         json_dict = make_json_dict(reaction_type)
         const_reaction_type = ReactionType.de_json(json_dict, offline_bot)
@@ -252,8 +250,6 @@ def test_de_json(self, offline_bot):
         assert reaction_count.type.emoji == self.type.emoji
         assert reaction_count.total_count == self.total_count
 
-        assert ReactionCount.de_json(None, offline_bot) is None
-
     def test_to_dict(self, reaction_count):
         reaction_count_dict = reaction_count.to_dict()
 
diff --git a/tests/test_reply.py b/tests/test_reply.py
index 7cf83c8b1e4..ad95de4bfe6 100644
--- a/tests/test_reply.py
+++ b/tests/test_reply.py
@@ -93,8 +93,6 @@ def test_de_json(self, offline_bot):
         assert external_reply_info.giveaway == self.giveaway
         assert external_reply_info.paid_media == self.paid_media
 
-        assert ExternalReplyInfo.de_json(None, offline_bot) is None
-
     def test_to_dict(self, external_reply_info):
         ext_reply_info_dict = external_reply_info.to_dict()
 
@@ -167,8 +165,6 @@ def test_de_json(self, offline_bot):
         assert text_quote.entities == tuple(self.entities)
         assert text_quote.is_manual == self.is_manual
 
-        assert TextQuote.de_json(None, offline_bot) is None
-
     def test_to_dict(self, text_quote):
         text_quote_dict = text_quote.to_dict()
 
@@ -255,8 +251,6 @@ def test_de_json(self, offline_bot):
         assert reply_parameters.quote_entities == tuple(self.quote_entities)
         assert reply_parameters.quote_position == self.quote_position
 
-        assert ReplyParameters.de_json(None, offline_bot) is None
-
     def test_to_dict(self, reply_parameters):
         reply_parameters_dict = reply_parameters.to_dict()
 
diff --git a/tests/test_shared.py b/tests/test_shared.py
index 1e11e8f56f3..239e8600092 100644
--- a/tests/test_shared.py
+++ b/tests/test_shared.py
@@ -59,8 +59,6 @@ def test_de_json(self, offline_bot):
         assert users_shared.request_id == self.request_id
         assert users_shared.users == self.users
 
-        assert UsersShared.de_json({}, offline_bot) is None
-
     def test_equality(self):
         a = UsersShared(self.request_id, users=self.users)
         b = UsersShared(self.request_id, users=self.users)
@@ -209,8 +207,6 @@ def test_de_json_all(self, offline_bot):
         assert shared_user.username == self.username
         assert shared_user.photo == self.photo
 
-        assert SharedUser.de_json({}, offline_bot) is None
-
     def test_equality(self, chat_shared):
         a = SharedUser(
             self.user_id,
diff --git a/tests/test_story.py b/tests/test_story.py
index 69c60289e79..f29c5c857ae 100644
--- a/tests/test_story.py
+++ b/tests/test_story.py
@@ -45,7 +45,6 @@ def test_de_json(self, offline_bot):
         assert story.chat == self.chat
         assert story.id == self.id
         assert isinstance(story, Story)
-        assert Story.de_json(None, offline_bot) is None
 
     def test_to_dict(self, story):
         story_dict = story.to_dict()
diff --git a/tests/test_telegramobject.py b/tests/test_telegramobject.py
index 8496a9f1ca0..722acdb1624 100644
--- a/tests/test_telegramobject.py
+++ b/tests/test_telegramobject.py
@@ -103,7 +103,7 @@ def __init__(self, arg: int, **kwargs):
 
                 self._id_attrs = (self.arg,)
 
-        assert SubClass.de_list([{"arg": 1}, None, {"arg": 2}, None], bot) == (
+        assert SubClass.de_list([{"arg": 1}, {"arg": 2}], bot) == (
             SubClass(1),
             SubClass(2),
         )
diff --git a/tests/test_update.py b/tests/test_update.py
index d3018e8b6fe..46fdb88c450 100644
--- a/tests/test_update.py
+++ b/tests/test_update.py
@@ -47,6 +47,7 @@
     PreCheckoutQuery,
     ReactionCount,
     ReactionTypeEmoji,
+    ShippingAddress,
     ShippingQuery,
     Update,
     User,
@@ -158,7 +159,11 @@
     {"edited_channel_post": channel_post},
     {"inline_query": InlineQuery(1, User(1, "", False), "", "")},
     {"chosen_inline_result": ChosenInlineResult("id", User(1, "", False), "")},
-    {"shipping_query": ShippingQuery("id", User(1, "", False), "", None)},
+    {
+        "shipping_query": ShippingQuery(
+            "id", User(1, "", False), "", ShippingAddress("", "", "", "", "", "")
+        )
+    },
     {"pre_checkout_query": PreCheckoutQuery("id", User(1, "", False), "", 0, "")},
     {"poll": Poll("id", "?", [PollOption(".", 1)], False, False, False, Poll.REGULAR, True)},
     {
@@ -252,11 +257,6 @@ def test_de_json(self, offline_bot, paramdict):
                 assert getattr(update, _type) == paramdict[_type]
         assert i == 1
 
-    def test_update_de_json_empty(self, offline_bot):
-        update = Update.de_json(None, offline_bot)
-
-        assert update is None
-
     def test_to_dict(self, update):
         update_dict = update.to_dict()
 
diff --git a/tests/test_videochat.py b/tests/test_videochat.py
index af268c0863f..57d91003c29 100644
--- a/tests/test_videochat.py
+++ b/tests/test_videochat.py
@@ -162,7 +162,6 @@ def test_expected_values(self):
         assert VideoChatScheduled(self.start_date).start_date == self.start_date
 
     def test_de_json(self, offline_bot):
-        assert VideoChatScheduled.de_json({}, bot=offline_bot) is None
 
         json_dict = {"start_date": to_timestamp(self.start_date)}
         video_chat_scheduled = VideoChatScheduled.de_json(json_dict, offline_bot)
diff --git a/tests/test_webhookinfo.py b/tests/test_webhookinfo.py
index 92c1f3be445..56725e7f67f 100644
--- a/tests/test_webhookinfo.py
+++ b/tests/test_webhookinfo.py
@@ -99,9 +99,6 @@ def test_de_json(self, offline_bot):
             self.last_synchronization_error_date
         )
 
-        none = WebhookInfo.de_json(None, offline_bot)
-        assert none is None
-
     def test_de_json_localization(self, offline_bot, raw_bot, tz_bot):
         json_dict = {
             "url": self.url,