diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py
index 961848374ed44824861d6c54a79053c07a93776b..d336b00cde8632a7d7ef4279cdbe8cc908edc32e 100644
--- a/helios/datatypes/__init__.py
+++ b/helios/datatypes/__init__.py
@@ -1,5 +1,29 @@
 """
 The Helios datatypes are RDF datatypes that map to JSON-LD
+
+A datatype object wraps another object and performs serialization / de-serialization
+to and from that object. For example, a Helios election is treated as follows:
+
+  helios_election = get_current_election() # returns a helios.models.Election object
+  
+  # dispatch to the right contructor via factory pattern
+  # LDObject knows about base classes like Election, Voter, CastVote, Trustee
+  # and it looks for the datatype field within the wrapped object to determine
+  # which LDObject subclass to dispatch to.
+  ld_object = LDObject.instantiate(helios_election)
+
+  # get some JSON-LD
+  ld_object.serialize()
+
+And when data comes in:
+
+  # the type is the base type, Election, CastVote, Trustee, ...
+  # if this is raw JSON, then this invokes the legacy LDObject parser
+  # if this is JSON-LD, then it finds the right LDObject based on the declared type
+  # in the JSON-LD.
+  # the optional type variable is necessary for legacy objects (otherwise, what is the type?)
+  # but is not necessary for full JSON-LD objects.
+  LDObject.deserialize(json_string, type=...)
 """
 
 from helios import utils
@@ -19,6 +43,19 @@ class LDObject(object):
     # fields to serialize
     FIELDS = []
 
+    @classmethod
+    def instantiate(cls, obj):
+        if not hasattr(obj, 'datatype'):
+            raise Exception("no datatype found")
+
+        # parse datatype string "v31/Election" --> from v31 import Election
+        parsed_datatype = obj.datatype.split("/")
+
+        # construct it
+        dynamic_cls = getattr(__import__(".".join(parsed_datatype[:-1]), globals(), locals(), [], level=-1), parsed_datatype[len(parsed_datatype)-1])
+
+        return dynamic_cls(obj)
+
     def set_from_args(self, **kwargs):
         for f in self.FIELDS:
             if kwargs.has_key(f):
diff --git a/helios/migrations/0002_v3_1_new_election_and_voter_fields.py b/helios/migrations/0002_v3_1_new_election_and_voter_fields.py
index e75643d805af504ee5b6ec6275150b2c876b8062..56bd20b9d90cdb3d76ed4693ad02cc91a6e72b84 100644
--- a/helios/migrations/0002_v3_1_new_election_and_voter_fields.py
+++ b/helios/migrations/0002_v3_1_new_election_and_voter_fields.py
@@ -24,8 +24,7 @@ class Migration(SchemaMigration):
         db.add_column('helios_voter', 'voter_email', self.gf('django.db.models.fields.CharField')(max_length=250, null=True), keep_default=False)
 
         # Adding field 'Election.datatype'
-        # manually tweaked default value to ensure proper datatype for older elections
-        db.add_column('helios_election', 'datatype', self.gf('django.db.models.fields.CharField')(default='legacy/election', max_length=250), keep_default=False)
+        db.add_column('helios_election', 'datatype', self.gf('django.db.models.fields.CharField')(default='2011/01/Election', max_length=250), keep_default=False)
 
         # Adding field 'Election.election_type'
         db.add_column('helios_election', 'election_type', self.gf('django.db.models.fields.CharField')(default='election', max_length=250), keep_default=False)
diff --git a/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py b/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py
index 8053a8e882d71390424ffb1c7f2c6625a80d14fd..762f72b549ee4dd6c8335bd85a1c1c323820a073 100644
--- a/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py
+++ b/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py
@@ -72,7 +72,7 @@ class Migration(DataMigration):
             'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
             'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
             'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
-            'datatype': ('django.db.models.fields.CharField', [], {'default': "'2011/01/election'", 'max_length': '250'}),
+            'datatype': ('django.db.models.fields.CharField', [], {'default': "'2011/01/Election'", 'max_length': '250'}),
             'description': ('django.db.models.fields.TextField', [], {}),
             'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}),
             'eligibility': ('auth.jsonfield.JSONField', [], {'null': 'True'}),
diff --git a/helios/models.py b/helios/models.py
index 6e0e3f92bd9d77f344c0c9bdd511c50a4083c125..a8abc3a9aa384792428af4b46cb779bdcc7777cd 100644
--- a/helios/models.py
+++ b/helios/models.py
@@ -37,7 +37,7 @@ class Election(models.Model, electionalgs.Election):
   # code, both for crypto and serialization
   # v3 and prior have a datatype of "legacy/election"
   # v3.1 and above have a specific datatype of "2011/01/election"
-  datatype = models.CharField(max_length=250, null=False, default="2011/01/election")
+  datatype = models.CharField(max_length=250, null=False, default="2011/01/Election")
   
   short_name = models.CharField(max_length=100)
   name = models.CharField(max_length=250)
diff --git a/helios/tests.py b/helios/tests.py
index 10fdb91562e1ef258c510a9283e55634de96716f..a6e52525e7717534c168daf53501e2c169aa18e0 100644
--- a/helios/tests.py
+++ b/helios/tests.py
@@ -5,6 +5,8 @@ Unit Tests for Helios
 import unittest, datetime, re
 
 import models
+import datatypes
+
 from auth import models as auth_models
 from views import ELGAMAL_PARAMS
 import views
@@ -213,6 +215,17 @@ class CastVoteModelTests(TestCase):
 
     def test_cast_vote(self):
         assert False
+
+class DatatypeTests(TestCase):
+    fixtures = ['election.json']
+
+    def setUp(self):
+        self.election = models.Election.objects.all()[0]
+
+    def test_instantiate(self):
+        ld_obj = datatypes.LDObject.instantiate(self.election)
+
+
 ##
 ## Black box tests
 ##