Explicitly convert dbus.Booleans to bools

The dbus.Boolean data type that the dbus python module uses for
booleans is based on an int, so the JSON encoder class serializes
it to a 1 and 0 instead of the native JSON bool presentation of
true and false.  Though the encoder module does allow for custom
serialization functions, it only does so for data types that it
doesn't recognize, and it does recognize a dbus.Boolean though it
thinks it's an int.

To get around this limitation, walk the response dictionary
object and convert all dbus.Booleans to bools before encoding
the response.  The only time that data is copied is when a boolean
is in a dbus.Struct, which is an immutable tuple, so it will be
made into a list during revision before converting it back when
complete.

There is a slight performance penalty to pay for this.  Measurements
showed enumerating the whole /xyz/openbmc_project tree take about
1 second longer.

Resolves openbmc/openbmc#3154

Tested:  Lots of REST calls and output inspection.

Change-Id: I591f010798a80aeafd02289e3d35c335540f6562
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/module/obmc/wsgi/apps/rest_dbus.py b/module/obmc/wsgi/apps/rest_dbus.py
index 9cb69f0..badc550 100644
--- a/module/obmc/wsgi/apps/rest_dbus.py
+++ b/module/obmc/wsgi/apps/rest_dbus.py
@@ -1381,6 +1381,46 @@
     def __init__(self, app):
         app.install_error_callback(self.error_callback)
 
+    @staticmethod
+    def dbus_boolean_to_bool(data):
+        ''' Convert all dbus.Booleans to true/false instead of 1/0 as
+            the JSON encoder thinks they're ints.  Note that unlike
+            dicts and lists, tuples (from a dbus.Struct) are immutable
+            so they need special handling. '''
+
+        def walkdict(data):
+            for key, value in data.items():
+                if isinstance(value, dbus.Boolean):
+                    data[key] = bool(value)
+                elif isinstance(value, tuple):
+                    data[key] = walktuple(value)
+                else:
+                    JsonApiResponsePlugin.dbus_boolean_to_bool(value)
+
+        def walklist(data):
+            for i in range(len(data)):
+                if isinstance(data[i], dbus.Boolean):
+                    data[i] = bool(data[i])
+                elif isinstance(data[i], tuple):
+                    data[i] = walktuple(data[i])
+                else:
+                    JsonApiResponsePlugin.dbus_boolean_to_bool(data[i])
+
+        def walktuple(data):
+            new = []
+            for item in data:
+                if isinstance(item, dbus.Boolean):
+                    item = bool(item)
+                else:
+                    JsonApiResponsePlugin.dbus_boolean_to_bool(item)
+                new.append(item)
+            return tuple(new)
+
+        if isinstance(data, dict):
+            walkdict(data)
+        elif isinstance(data, list):
+            walklist(data)
+
     def apply(self, callback, route):
         skip = getattr(
             route.get_undecorated_callback(), 'suppress_json_resp', None)
@@ -1389,6 +1429,7 @@
 
         def wrap(*a, **kw):
             data = callback(*a, **kw)
+            JsonApiResponsePlugin.dbus_boolean_to_bool(data)
             if self.has_body():
                 resp = {'data': data}
                 resp['status'] = 'ok'