Repository: voxel51/fiftyone
Issue: dataset.delete_sample_fields() is not deleting all values

When running `dataset.delete_sample_fields("field")`, the field will be deleted. However, if a sample then recieves a new value for that field, all other samples are reverted to the value they had before the field was deleted. 

This can result in two samples having different value types for the same field. 
Recreate with the code:
```
dataset = fo.Dataset("example")

s1 = fo.Sample("s1.png")
s2 = fo.Sample("s2.png")

ids = dataset.add_samples([s1, s2])

s1["new_field"] = 1
dataset.delete_sample_fields("new_field")
s2["new_field"] = "str"

print(dataset[ids[0]].new_field)
Out:  1  ### This is an integer

print(dataset[ids[1]].new_field)
Out: "str" ### This is a string
```

Changed files:
fiftyone/core/sample.py, tests/unittests.py, fiftyone/core/dataset.py, fiftyone/core/odm/document.py

diff --git a/fiftyone/core/dataset.py b/fiftyone/core/dataset.py
index 7e0d4c313..86d4287e5 100644
--- a/fiftyone/core/dataset.py
+++ b/fiftyone/core/dataset.py
@@ -316,6 +316,7 @@ class Dataset(foc.SampleCollection, metaclass=DatasetSingleton):
             AttributeError: if the field does not exist
         """
         self._sample_doc_cls.delete_field(field_name)
+        fos.Sample._purge_field(self.name, field_name)
 
     def get_tags(self):
         """Returns the set of tags in the dataset.
diff --git a/fiftyone/core/odm/document.py b/fiftyone/core/odm/document.py
index 67424ad49..d9cfaf0ac 100644
--- a/fiftyone/core/odm/document.py
+++ b/fiftyone/core/odm/document.py
@@ -63,8 +63,6 @@ class SerializableDocument(object):
                 continue
 
             value = getattr(self, f)
-            if value is None:
-                continue
 
             if isinstance(value, ObjectId):
                 d[f] = str(value)
diff --git a/fiftyone/core/sample.py b/fiftyone/core/sample.py
index d4bdce200..d1b833f6e 100644
--- a/fiftyone/core/sample.py
+++ b/fiftyone/core/sample.py
@@ -21,7 +21,6 @@ from future.utils import itervalues
 
 from collections import defaultdict
 from copy import deepcopy
-import json
 import os
 import weakref
 
@@ -333,6 +332,17 @@ class Sample(object):
         for sample in cls._instances[dataset_name].values():
             sample.reload()
 
+    @classmethod
+    def _purge_field(cls, dataset_name, field_name):
+        """Remove any field values from samples that exist in memory.
+
+        Args:
+            dataset_name: the name of the dataset to reload.
+            field_name: the name of the field to purge.
+        """
+        for sample in cls._instances[dataset_name].values():
+            sample._doc._data.pop(field_name, None)
+
     def _delete(self):
         """Deletes the document from the database."""
         self._doc.delete()
diff --git a/tests/unittests.py b/tests/unittests.py
index dc06c7bab..117758f85 100644
--- a/tests/unittests.py
+++ b/tests/unittests.py
@@ -1062,6 +1062,26 @@ class SampleInDatasetTest(unittest.TestCase):
         self.assertIsNone(sample_copy.id)
         self.assertIsNone(sample_copy.dataset_name)
 
+    @drop_datasets
+    def test_in_memory_sample_fields(self):
+        """Ensure that any in memory samples have the field values purged when
+        a field is deleted.
+        """
+        dataset_name = self.test_in_memory_sample_fields.__name__
+        dataset = fo.Dataset(dataset_name)
+
+        s1 = fo.Sample("s1.png")
+        s2 = fo.Sample("s2.png")
+
+        dataset.add_samples([s1, s2])
+
+        s1["new_field"] = 51
+        dataset.delete_sample_field("new_field")
+        s2["new_field"] = "fiftyone"
+
+        self.assertIsNone(s1.new_field)
+        self.assertEqual(s2.new_field, "fiftyone")
+
 
 class LabelsTest(unittest.TestCase):
     @drop_datasets