Search code examples
pythonjsondjangodjango-modelsdjango-jsonfield

Django JSONField data with integer keys


I want to save a model instance with JSON dict with integer keys, like {2: 3}.

But after saving my dict turns into {"2": 3}. Is there any way to save integer keys into JSON?

class MyModel(models.Model):
    data = JSONField("data")

record = MyModel.objects.create(
    data={2: 3},
)
record.refresh_from_db()

print(record.data)

# > {"2": 3} 
# And I want record.data == {2: 3}

Behavior same for from django.contrib.postgres.fields import JSONField and for modern from django.db.models import JSONField


Solution

  • I want to save a model instance with JSON dict with integer keys, like {2: 3}.

    That is not possible: the JSON specifications say that keys are always strings. The JSON serializer in Python will convert the keys to their string counterparts (perhaps it was more appropriate to raise an error in that case).

    If all keys are integers, you could make a subclass of a JSONField that will automatically cast the keys to ints, for example:

    from django.db.models import JSONField
    
    def outer_json_is_object(value):
        if not isinstance(value, dict):
            raise ValidationError(_('Outer JSON item should be an object'), code='invalid_json_object')
    
    class IntKeyJSONField(JSONField):
    
        def __init__(self, *args, **kwargs):
            validators = kwargs.setdefault('validators', [])
            validators.append(outer_json_is_object)
            super().__init__(*args, **kwargs)
        
        def from_db_value(self, value, expression, connection):
            value = super().from_db_value(value, expression, connection)
            if value is not None:
                return { int(k): v for k, v in value.items() }

    then it will cast the outer keys to ints of a JSON object. You can thus then use an IntKeyJSONField, but this thus does not store the keys as integers, since again, that is simply not valid JSON, what we do is we post-process the dictionary when reading from JSON and thus replace the keys with their int counterparts.