Repository: microsoft/botbuilder-python

Issue:
Teams Task Module - Deserialization Error on Teams mobile app for iOS
## Version
botbuilder-integration-aiohttp 4.14.0
Python 3.8.6 

## Describe the bug
Error when loading Task Module on iOS iOS 14.8.1 / MS Teams v3.20.0

## To Reproduce
1. Deploy [sample bot 54.teams-task-module](https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/python/54.teams-task-module) 
2. Say hello and click on _Adaptive Card_ button
3. Deserialization Error when on iOS iOS 14.8.1 / Microsoft Teams v3.20.0
![image](https://user-images.githubusercontent.com/4013036/146412591-61399a75-d3d3-4eb6-a0ec-36ffa3cac54c.png)

## Traceback
_(file locations prefix intentionally removed)_
```
  File "test_teams_task/env/lib/site-packages/msrest/serialization.py", line 1293, in _deserialize
    found_value = key_extractor(attr, attr_desc, data)
  File "test_teams_task/env/lib/site-packages/msrest/serialization.py", line 1064, in rest_key_extractor
    return working_data.get(key)
AttributeError: 'str' object has no attribute 'get'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "test_teams_task/env/lib/site-packages/botbuilder/core/bot_adapter.py", line 129, in run_pipeline
    context, callback
  File "test_teams_task/env/lib/site-packages/botbuilder/core/middleware_set.py", line 69, in receive_activity_with_status
    return await self.receive_activity_internal(context, callback)
  File "test_teams_task/env/lib/site-packages/botbuilder/core/middleware_set.py", line 79, in receive_activity_internal
    return await callback(context)
  File "test_teams_task/env/lib/site-packages/botbuilder/core/activity_handler.py", line 78, in on_turn
    invoke_response = await self.on_invoke_activity(turn_context)
  File "test_teams_task/env/lib/site-packages/botbuilder/core/teams/teams_activity_handler.py", line 155, in on_invoke_activity
    TaskModuleRequest, turn_context.activity.value
  File "test_teams_task/env/lib/site-packages/botbuilder/core/serializer_helper.py", line 28, in deserializer_helper
    return deserializer(msrest_cls.__name__, dict_to_deserialize)
  File "test_teams_task/env/lib/site-packages/msrest/serialization.py", line 1233, in __call__
    return self._deserialize(target_obj, data)
  File "test_teams_task/env/lib/site-packages/msrest/serialization.py", line 1299, in _deserialize
    value = self.deserialize_data(raw_value, attr_desc['type'])
  File "test_teams_task/env/lib/site-packages/msrest/serialization.py", line 1468, in deserialize_data
    return self._deserialize(obj_type, data)
  File "test_teams_task/env/lib/site-packages/msrest/serialization.py", line 1303, in _deserialize
    raise_with_traceback(DeserializationError, msg, err)
  File "test_teams_task/env/lib/site-packages/msrest/exceptions.py", line 51, in raise_with_traceback
    raise error.with_traceback(exc_traceback)
  File "test_teams_task/env/lib/site-packages/msrest/serialization.py", line 1293, in _deserialize
    found_value = key_extractor(attr, attr_desc, data)
  File "test_teams_task/env/lib/site-packages/msrest/serialization.py", line 1064, in rest_key_extractor
    return working_data.get(key)
msrest.exceptions.DeserializationError: Unable to deserialize to object: type, AttributeError: 'str' object has no attribute 'get'
```

## Expected behavior
This sample bot raises no error when interacting on the following platforms:
- Windows 10 (Desktop app, Firefox, Chrome)
- macOS (Chrome)
- Android (Mobile app)
![image](https://user-images.githubusercontent.com/4013036/146413680-7bc42c4d-9876-4d18-9a61-7b94b4a5cccb.png)

It was possible to interact with Task Module on iOS iOS 14.8.1 / Microsoft Teams v3.20.0 when deploying these samples (python not included):
https://docs.microsoft.com/en-us/samples/officedev/microsoft-teams-samples/ms-teams-task-sample/

## Additional context
Initially the error was detected on a bot in production currently deployed in Azure. Since the error message is the same when running [bot sample 54.teams-task-module](https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/python/54.teams-task-module), for the sake of repro we can take this example. 



Discussion:
This issue could take up a couple of day to get repro. Actively working on it, will post an update when available. 
Was able to repro, this is due to a change in the Teams client for iOS. Will try to provide a fix soon. Thanks.
@axelsrz  Any updates on this issue?

Resolving patch:
diff --git a/libraries/botbuilder-core/botbuilder/core/serializer_helper.py b/libraries/botbuilder-core/botbuilder/core/serializer_helper.py
--- a/libraries/botbuilder-core/botbuilder/core/serializer_helper.py
+++ b/libraries/botbuilder-core/botbuilder/core/serializer_helper.py
@@ -1,6 +1,6 @@
 # Copyright (c) Microsoft Corporation. All rights reserved.
 # Licensed under the MIT License.
-
+from copy import copy
 from inspect import getmembers
 from typing import Type
 from enum import Enum
@@ -25,6 +25,9 @@
 
 def deserializer_helper(msrest_cls: Type[Model], dict_to_deserialize: dict) -> Model:
     deserializer = Deserializer(DEPENDICIES_DICT)
+    _clean_data_for_serialization(
+        deserializer.dependencies[msrest_cls.__name__], dict_to_deserialize
+    )
     return deserializer(msrest_cls.__name__, dict_to_deserialize)
 
 
@@ -35,3 +38,21 @@ def serializer_helper(object_to_serialize: Model) -> dict:
     serializer = Serializer(DEPENDICIES_DICT)
     # pylint: disable=protected-access
     return serializer._serialize(object_to_serialize)
+
+
+def _clean_data_for_serialization(msrest_cls: Type[Model], dict_to_deserialize: dict):
+    # pylint: disable=protected-access
+    # Clean channel response of empty strings for expected objects.
+    if not isinstance(dict_to_deserialize, dict):
+        return
+    serialization_model = copy(msrest_cls._attribute_map)
+    for key, value in msrest_cls._attribute_map.items():
+        if key != value["key"]:
+            serialization_model[value["key"]] = value
+    for prop, prop_value in dict_to_deserialize.items():
+        if (
+            prop in serialization_model
+            and serialization_model[prop]["type"] in DEPENDICIES_DICT
+            and not prop_value
+        ):
+            dict_to_deserialize[prop] = None


Test patch:
diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py
--- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py
+++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py
@@ -1003,6 +1003,25 @@ async def test_on_teams_task_module_fetch(self):
         assert bot.record[0] == "on_invoke_activity"
         assert bot.record[1] == "on_teams_task_module_fetch"
 
+    async def test_on_teams_task_module_fetch_none_as_empty(self):
+        # Arrange
+        activity = Activity(
+            type=ActivityTypes.invoke,
+            name="task/fetch",
+            value={"data": {"key": "value"}, "context": "",},
+        )
+
+        turn_context = TurnContext(SimpleAdapter(), activity)
+
+        # Act
+        bot = TestingTeamsActivityHandler()
+        await bot.on_turn(turn_context)
+
+        # Assert
+        assert len(bot.record) == 2
+        assert bot.record[0] == "on_invoke_activity"
+        assert bot.record[1] == "on_teams_task_module_fetch"
+
     async def test_on_teams_task_module_submit(self):
         # Arrange
         activity = Activity(