Two-faced JSONField#

My team develops a rather complex Django service, that uses django-jsonfield in multiple places for some while.

Recently we added a new feature - sending invitation emails. It was rather simple task, because we took magnificient django-post-office. Everything went fine, until we realized that unit tests for models, that has JSONFields started to fail with cryptic errors.

Turned out, that django-post-office depends on jsonfield package. And this package is slightly different from the one we already use. They conflict, because both packages install into jsonfield directory. They have exactly the same public interface and are interchangeble in most cases. And pip does nothing to handle or detect this conflict and mixes both packages into single directory producing unpredictable Frankenshtein.

I dug a little summary on them:

  1. Maintainer: Dan Koch Source: Current Version: 2.0.2 PyPI:

  2. Maintainer: Matthew Schinckel Source: Current version: 1.0.1 PyPI:

Post office uses the first one, by Dan Koch. It perfectly fulfills their needs. But it doesn’t work correctly with django.utils.model_to_dict - it returns dumped JSON string representation, instead of object.

Here is the code responsible for this [mis]behavior:

def value_from_object(self, obj, dump=True):
    value = super(JSONFieldBase, self).value_from_object(obj)
    if self.null and value is None:
        return None
    return self.dumps_for_display(value) if dump else value

It dumps value by default, while model_to_dict expects it to return value unchanged.

JSONField by Matthew Schinckel doesn’t override value_from_object and works as Django expects.

Once we discovered the full story we had three options:

  1. Switch from django-post-office to another library that doesn’t have dependency conflicts.

  2. Extract email sending to separate service and remove dependency conflict.

  3. Inherit Koch’s jsonfield and override problematic method.

I went with option 3. At the same time I opened a PR to django-post-office to switch to the one and true django-jsonfield.