Repository: dapr/python-sdk

Issue:
Support default json serializer in DaprClient and add .json helper
## Describe the proposal

In [the convenient layer spec](https://github.com/dapr/python-sdk/issues/67), DaprClient doesn't have the default json serializer, but let users to handle JSON serialization. Like the other python pkgs, such as flask, requests, etc, DaprClient can have default json serializer to serialize and deserialize python dict type or primitive objects and provide .json() helper to DaprResponse and DataContent in AppCallback Layer. It will improve development experience instead of serializing and deserializing data object.

Comments:

Patch:
diff --git a/dapr/clients/grpc/_response.py b/dapr/clients/grpc/_response.py
index 383a67a..d037a3f 100644
--- a/dapr/clients/grpc/_response.py
+++ b/dapr/clients/grpc/_response.py
@@ -20,6 +20,8 @@ from dapr.clients.grpc._helpers import (
     unpack,
 )
 
+import json
+
 
 class DaprResponse:
     """A base class for Dapr Response.
@@ -153,6 +155,15 @@ class InvokeMethodResponse(DaprResponse):
         """
         return to_str(self.data)
 
+    def json(self) -> Dict[str, object]:
+        """Gets the content as json if the response data content is not a serialized
+        protocol buffer message.
+
+        Returns:
+            str: [description]
+        """
+        return json.loads(to_str(self.data))
+
     @property
     def content_type(self) -> Optional[str]:
         """Gets the content type of content attribute."""
@@ -233,6 +244,10 @@ class BindingResponse(DaprResponse):
         """Gets content as str."""
         return to_str(self._data)
 
+    def json(self) -> Dict[str, object]:
+        """Gets content as deserialized JSON dictionary."""
+        return json.loads(to_str(self._data))
+
     @property
     def data(self) -> bytes:
         """Gets raw bytes data."""
@@ -340,6 +355,10 @@ class StateResponse(DaprResponse):
         """Gets content as str."""
         return to_str(self._data)
 
+    def json(self) -> Dict[str, object]:
+        """Gets content as deserialized JSON dictionary."""
+        return json.loads(to_str(self._data))
+
     @property
     def etag(self) -> str:
         """Gets etag."""
@@ -389,6 +408,10 @@ class BulkStateItem:
         """Gets content as str."""
         return to_str(self._data)
 
+    def json(self) -> Dict[str, object]:
+        """Gets content as deserialized JSON dictionary."""
+        return json.loads(to_str(self._data))
+
     @property
     def key(self) -> str:
         """Gets key."""


Test patch:
diff --git a/tests/clients/test_dapr_grpc_response.py b/tests/clients/test_dapr_grpc_response.py
index 20d5fc4..afdabe6 100644
--- a/tests/clients/test_dapr_grpc_response.py
+++ b/tests/clients/test_dapr_grpc_response.py
@@ -9,7 +9,11 @@ import unittest
 
 from google.protobuf.any_pb2 import Any as GrpcAny
 
-from dapr.clients.grpc._response import DaprResponse, InvokeMethodResponse, BindingResponse
+from dapr.clients.grpc._response import (
+    DaprResponse, InvokeMethodResponse, BindingResponse, StateResponse,
+    BulkStateItem
+)
+
 from dapr.proto import common_v1
 
 
@@ -64,6 +68,10 @@ class InvokeMethodResponseTests(unittest.TestCase):
         self.assertEqual('hello dapr', resp.text())
         self.assertEqual('application/json', resp.content_type)
 
+    def test_json_data(self):
+        resp = InvokeMethodResponse(data=b'{ "status": "ok" }', content_type='application/json')
+        self.assertEqual({'status': 'ok'}, resp.json())
+
     def test_unpack(self):
         # arrange
         fake_req = common_v1.InvokeRequest(method="test")
@@ -84,6 +92,10 @@ class InvokeBindingResponseTests(unittest.TestCase):
         self.assertEqual(b'data', resp.data)
         self.assertEqual('data', resp.text())
 
+    def test_json_data(self):
+        resp = BindingResponse(data=b'{"status": "ok"}', binding_metadata={})
+        self.assertEqual({'status': 'ok'}, resp.json())
+
     def test_metadata(self):
         resp = BindingResponse(data=b'data', binding_metadata={'status': 'ok'})
         self.assertEqual({'status': 'ok'}, resp.binding_metadata)
@@ -91,5 +103,22 @@ class InvokeBindingResponseTests(unittest.TestCase):
         self.assertEqual('data', resp.text())
 
 
+class StateResponseTests(unittest.TestCase):
+    def test_data(self):
+        resp = StateResponse(data=b'hello dapr')
+        self.assertEqual('hello dapr', resp.text())
+        self.assertEqual(b'hello dapr', resp.data)
+
+    def test_json_data(self):
+        resp = StateResponse(data=b'{"status": "ok"}')
+        self.assertEqual({'status': 'ok'}, resp.json())
+
+
+class BulkStateItemTests(unittest.TestCase):
+    def test_data(self):
+        item = BulkStateItem(key='item1', data=b'{ "status": "ok" }')
+        self.assertEqual({'status': 'ok'}, item.json())
+
+
 if __name__ == '__main__':
     unittest.main()