How to preserve key order in a Django JSONField

1.4k views Asked by At

I am having a hard time preserving the order of keys in a JSON object stored in a Django JSONField. I have tried using a custom encoder and decoder as per the docs, but the JSON object keeps re-ordeing itself:

>>>from models import MyModel
>>>my_dict = {"c": 3, "b": 2, "a": 1}
>>>my_model = MyModel()
>>>my_model.my_field = my_dict
>>>my_model.save()
>>>my_model.refresh_from_db()
>>>my_model.my_field
OrderedDict([('a',1), ('b', 2), ('c', 3)])

I would have expected it to return OrderedDict([('c',3), ('b', 2), ('a', 1)]).

Here is what I've tried so far:

models.py:

import collections
import json
from django.db import models

class OrderedEncoder(json.JSONEncoder):
  def encode(self, o):
    if isinstance(o, collections.OrderedDict):
      encoded_objs = [
        f"{self.encode(k)}: {self.encode(v)}"
        for k, v in o.items()
      ]
      return f"{{{','.join(encoded_objs)}}}"
   else:
     return super().encode(o)

class OrderedDecoder(json.JSONDecoder):
    def __init__(self, *args, **kwargs):
        default_kwargs = {
            "object_pairs_hook": collections.OrderedDict
        }
        default_kwargs.update(kwargs)
        super().__init__(*args, **default_kwargs)

class MyModel(models.Model):
  my_field = models.JSONField(encoder=OrderedEncoder, decoder=OrderedDecoder)

Any ideas?

1

There are 1 answers

0
Nicolai Lindholm On

Had the same issue and I did manage to resolve it by. Not sure if it's best approach but it solved my issue being the same that the order of the key-values was different.

models.py:

import collections
import json

class myModel(models.Model):
    _field_to_contain_ordered_dict = models.TextField(
        null=False,
        blank=False,
    )

    @property
    def my_ordereddict_field(self):
        decoder = json.JSONDecoder(object_pairs_hook=collections.OrderedDict)
        # Below is a small hack to ensure the string is formatted as json
        # can be very peculiar with ' and "
        json_data = json.dumps(ast.literal_eval(self._field_to_contain_ordered_dict))
        response_ordereddict = decoder.decode(json_data)

        return response_ordereddict

    @my_ordereddict_field.setter
    def my_ordereddict_field(self, value):
        self._field_to_contain_ordered_dict = value

then in my code where i access myModel.my_ordereddict_field I get the correct sequence of item.

To initialise in code

my_model = myModel.objects.create(
    my_ordereddict_field={
        "Last Name": "Stark",
        "First Name": "Arija",
        "Level1": "my level 1",
        "Level2": "my level 2",
        "Level3": "my level 3",
        "Level4": "my level 4",
     }
)

my_model.refresh_from_db()

Then if you check type(my_model.my_ordereddict_field) you have OrderedDict and the sequence is correct