Repository: cwacek/python-jsonschema-objects

Issue:
Add option for not removing null values on serialization
**What are you trying to do?**
If you initalize a schema with null values, after serialziation they are removed.

```python
# test.py
import os
import python_jsonschema_objects as pjs

dir_path = os.path.dirname(os.path.realpath(__file__))
SCHEMA = os.path.join(dir_path, 'schema.json')


def get_namespace():
    builder = pjs.ObjectBuilder(SCHEMA)
    ns = builder.build_classes()
    return ns


def schema_defaults():
    ns = get_namespace()
    test = ns.Test()
    print(f'Name is {test.name}')
    print(f'Id is {test.id}')
    print(test.serialize())


if __name__ == "__main__":
	schema_defaults()
```
 ### Schema

```json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "$id": "schema.json",
  "type": "object",
  "definitions": {
    "test": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "default": "String"
        },
        "id": {
          "type": "null",
          "default": null
        }
      }
    }
  }
}
```

### Output

```bash
# python test.py
Name is String2
Id is None
{"name": "String"}
```

### Summary

I would except to have `{"name": "String", "id": null}` as the serialized json.   
The [corresponding lines of code](https://github.com/cwacek/python-jsonschema-objects/blob/master/python_jsonschema_objects/util.py#L56).   

I think it makes sense to have an option while serializing to specify if you want to remove null values or not.

Discussion:

Patch:
diff --git a/python_jsonschema_objects/classbuilder.py b/python_jsonschema_objects/classbuilder.py
index 0ed8a33..157471b 100644
--- a/python_jsonschema_objects/classbuilder.py
+++ b/python_jsonschema_objects/classbuilder.py
@@ -62,7 +62,10 @@ class ProtocolBase(collections.abc.MutableMapping):
                 out[prop] = [getattr(x, "for_json", lambda: x)() for x in propval]
             elif isinstance(propval, (ProtocolBase, LiteralValue)):
                 out[prop] = propval.as_dict()
-            elif propval is not None:
+            elif (
+                propval is not None
+                or self.__propinfo__[prop].get("type", None) == "null"
+            ):
                 out[prop] = propval
 
         return out
@@ -151,9 +154,6 @@ class ProtocolBase(collections.abc.MutableMapping):
         obj = None
         validation_errors = []
         for klass in valid_types:
-            logger.debug(
-                util.lazy_format("Attempting to instantiate {0} as {1}", cls, klass)
-            )
             try:
                 obj = klass(**props)
                 obj.validate()
@@ -171,6 +171,7 @@ class ProtocolBase(collections.abc.MutableMapping):
         return obj
 
     def __init__(self, **props):
+        logger.debug(util.lazy_format("Creating '{0}'", self.__class__))
         self._extended_properties = dict()
         self._properties = dict(
             zip(
@@ -649,6 +650,14 @@ class ClassBuilder(object):
 
             # Set default value, even if None
             if "default" in detail:
+                logger.debug(
+                    util.lazy_format(
+                        "Setting default for {0}.{1} to: {2}",
+                        nm,
+                        prop,
+                        detail["default"],
+                    )
+                )
                 defaults.add(prop)
 
             if detail.get("type", None) == "object":
diff --git a/python_jsonschema_objects/util.py b/python_jsonschema_objects/util.py
index ddc741c..c40174a 100644
--- a/python_jsonschema_objects/util.py
+++ b/python_jsonschema_objects/util.py
@@ -45,21 +45,15 @@ class ProtocolJSONEncoder(json.JSONEncoder):
         from python_jsonschema_objects import classbuilder
         from python_jsonschema_objects import wrapper_types
 
-        if isinstance(obj, classbuilder.LiteralValue):
-            return obj._value
-        if isinstance(obj, wrapper_types.ArrayWrapper):
+        if isinstance(
+            obj,
+            (
+                wrapper_types.ArrayWrapper,
+                classbuilder.ProtocolBase,
+                classbuilder.LiteralValue,
+            ),
+        ):
             return obj.for_json()
-        if isinstance(obj, classbuilder.ProtocolBase):
-            props = {}
-            for raw, trans in six.iteritems(obj.__prop_names__):
-                props[raw] = getattr(obj, trans)
-                if props[raw] is None:
-                    del props[raw]
-            for raw, data in six.iteritems(obj._extended_properties):
-                props[raw] = data
-                if props[raw] is None:
-                    del props[raw]
-            return props
         else:
             return json.JSONEncoder.default(self, obj)
 


Test patch:
diff --git a/test/test_regression_218.py b/test/test_regression_218.py
new file mode 100644
index 0000000..6d0e2a6
--- /dev/null
+++ b/test/test_regression_218.py
@@ -0,0 +1,57 @@
+import pytest
+import json
+import python_jsonschema_objects as pjo
+
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+logging.getLogger().setLevel(logging.DEBUG)
+
+schema = """
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "$id": "schema.json",
+  "title":"Null Test",
+  "type": "object",
+  "$ref": "#/definitions/test",
+  "definitions": {
+    "test": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "default": "String"
+        },
+        "id": {
+          "type": "null",
+          "default": null
+        }
+      }
+    }
+  }
+}
+"""
+
+
+@pytest.fixture
+def schema_json():
+    return json.loads(schema)
+
+
+@pytest.fixture
+def ns(schema_json):
+    builder = pjo.ObjectBuilder(schema_json)
+    ns = builder.build_classes()
+    return ns
+
+
+def test_defaults_serialize_for_nullable_types(ns):
+    logging.basicConfig(level=logging.DEBUG)
+    logging.getLogger().setLevel(logging.DEBUG)
+    thing1 = ns.NullTest()
+
+    serialized = thing1.as_dict()
+    print(serialized)
+    assert json.dumps(serialized) == """{"name": "String", "id": null}"""
+    serialized = thing1.serialize()
+    assert serialized == """{"name": "String", "id": null}"""