Repository: Azure/msrest-for-python

Issue:
Error when serializing flattened properties with null value as the container object
i found the msrest will report error when serializing flattened properties with null value as the container object, could you help to check? I tried to look at the msrest code, and seems the situation when 'data' is null is not handled in the last screenshot below. thanks.
![image](https://user-images.githubusercontent.com/6038235/78621314-e6cf1980-78b4-11ea-9316-58bee4dbbb38.png)
![image](https://user-images.githubusercontent.com/6038235/78621324-ef275480-78b4-11ea-94b3-29b6350b7218.png)
![image](https://user-images.githubusercontent.com/6038235/78621335-f64e6280-78b4-11ea-972b-b062f01efeab.png)


Discussion:
codecov-io: # [Codecov](https://codecov.io/gh/Azure/msrest-for-python/pull/198?src=pr&el=h1) Report
> Merging [#198](https://codecov.io/gh/Azure/msrest-for-python/pull/198?src=pr&el=desc) into [master](https://codecov.io/gh/Azure/msrest-for-python/commit/df4cea0e45976951700e21d5c192da372754443c&el=desc) will **decrease** coverage by `0.29%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/Azure/msrest-for-python/pull/198/graphs/tree.svg?width=650&height=150&src=pr&token=Lny6ZO1MUF)](https://codecov.io/gh/Azure/msrest-for-python/pull/198?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #198      +/-   ##
==========================================
- Coverage   87.64%   87.34%   -0.30%     
==========================================
  Files          25       25              
  Lines        2638     2640       +2     
==========================================
- Hits         2312     2306       -6     
- Misses        326      334       +8     
```


| [Impacted Files](https://codecov.io/gh/Azure/msrest-for-python/pull/198?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [msrest/serialization.py](https://codecov.io/gh/Azure/msrest-for-python/pull/198/diff?src=pr&el=tree#diff-bXNyZXN0L3NlcmlhbGl6YXRpb24ucHk=) | `91.06% <100.00%> (-0.19%)` | :arrow_down: |
| [msrest/async\_client.py](https://codecov.io/gh/Azure/msrest-for-python/pull/198/diff?src=pr&el=tree#diff-bXNyZXN0L2FzeW5jX2NsaWVudC5weQ==) | `77.27% <0.00%> (-6.82%)` | :arrow_down: |
| [msrest/exceptions.py](https://codecov.io/gh/Azure/msrest-for-python/pull/198/diff?src=pr&el=tree#diff-bXNyZXN0L2V4Y2VwdGlvbnMucHk=) | `97.46% <0.00%> (-1.27%)` | :arrow_down: |
| [msrest/pipeline/universal.py](https://codecov.io/gh/Azure/msrest-for-python/pull/198/diff?src=pr&el=tree#diff-bXNyZXN0L3BpcGVsaW5lL3VuaXZlcnNhbC5weQ==) | `94.11% <0.00%> (-0.99%)` | :arrow_down: |
| [msrest/universal\_http/requests.py](https://codecov.io/gh/Azure/msrest-for-python/pull/198/diff?src=pr&el=tree#diff-bXNyZXN0L3VuaXZlcnNhbF9odHRwL3JlcXVlc3RzLnB5) | `84.65% <0.00%> (-0.53%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Azure/msrest-for-python/pull/198?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Azure/msrest-for-python/pull/198?src=pr&el=footer). Last update [df4cea0...fe9b944](https://codecov.io/gh/Azure/msrest-for-python/pull/198?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/msrest/serialization.py b/msrest/serialization.py
index 27fb7e9..59187e1 100644
--- a/msrest/serialization.py
+++ b/msrest/serialization.py
@@ -1108,6 +1108,11 @@ def rest_key_extractor(attr, attr_desc, data):
             break
         working_key = _decode_attribute_map_key(dict_keys[0])
         working_data = working_data.get(working_key, data)
+        if working_data is None:
+            # If at any point while following flatten JSON path see None, it means
+            # that all properties under are None as well
+            # https://github.com/Azure/msrest-for-python/issues/197
+            return None
         key = '.'.join(dict_keys[1:])
 
     return working_data.get(key)
@@ -1123,6 +1128,11 @@ def rest_key_case_insensitive_extractor(attr, attr_desc, data):
             break
         working_key = _decode_attribute_map_key(dict_keys[0])
         working_data = attribute_key_case_insensitive_extractor(working_key, None, working_data)
+        if working_data is None:
+            # If at any point while following flatten JSON path see None, it means
+            # that all properties under are None as well
+            # https://github.com/Azure/msrest-for-python/issues/197
+            return None
         key = '.'.join(dict_keys[1:])
 
     if working_data:


Test patch:
diff --git a/tests/test_serialization.py b/tests/test_serialization.py
index 4b3f537..12a0042 100644
--- a/tests/test_serialization.py
+++ b/tests/test_serialization.py
@@ -1527,6 +1527,46 @@ class TestRuntimeDeserialized(unittest.TestCase):
         obj = TestObj.from_dict({'name': 'ab'})
         self.assertEqual('ab', obj.name)
 
+    def test_deserialize_flattening(self):
+        # https://github.com/Azure/msrest-for-python/issues/197
+
+        json_body = {
+            "properties" : {
+                "properties": None
+            }
+        }
+
+        class ComputeResource(Model):
+
+            _attribute_map = {
+                'properties': {'key': 'properties', 'type': 'VirtualMachine'},
+            }
+
+            def __init__(self, properties=None, **kwargs):
+                self.properties = properties
+
+        class VirtualMachine(Model):
+
+            _attribute_map = {
+                'virtual_machine_size': {'key': 'properties.virtualMachineSize', 'type': 'str'},
+                'ssh_port': {'key': 'properties.sshPort', 'type': 'int'},
+                'address': {'key': 'properties.address', 'type': 'str'},
+                'administrator_account': {'key': 'properties.administratorAccount', 'type': 'VirtualMachineSshCredentials'},
+            }
+
+            def __init__(self, **kwargs):
+                super(VirtualMachine, self).__init__(**kwargs)
+                self.virtual_machine_size = kwargs.get('virtual_machine_size', None)
+                self.ssh_port = kwargs.get('ssh_port', None)
+                self.address = kwargs.get('address', None)
+                self.administrator_account = kwargs.get('administrator_account', None)
+
+        d = Deserializer({
+            'ComputeResource': ComputeResource,
+            'VirtualMachine': VirtualMachine,
+        })
+        response = d(ComputeResource, json.dumps(json_body), 'application/json')
+
     def test_deserialize_storage(self):
         StorageAccount = storage_models.StorageAccount