196

I'm using json.dumps to convert into json like

countries.append({"id":row.id,"name":row.name,"timezone":row.timezone})
print json.dumps(countries)

The result i have is:

[
   {"timezone": 4, "id": 1, "name": "Mauritius"}, 
   {"timezone": 2, "id": 2, "name": "France"}, 
   {"timezone": 1, "id": 3, "name": "England"}, 
   {"timezone": -4, "id": 4, "name": "USA"}
]

I want to have the keys in the following order: id, name, timezone - but instead I have timezone, id, name.

How should I fix this?

Belphegor
  • 4,456
  • 11
  • 34
  • 59
Noor
  • 19,638
  • 38
  • 136
  • 254

8 Answers8

307

Both Python dict (before Python 3.7) and JSON object are unordered collections. You could pass sort_keys parameter, to sort the keys:

>>> import json
>>> json.dumps({'a': 1, 'b': 2})
'{"b": 2, "a": 1}'
>>> json.dumps({'a': 1, 'b': 2}, sort_keys=True)
'{"a": 1, "b": 2}'

If you need a particular order; you could use collections.OrderedDict:

>>> from collections import OrderedDict
>>> json.dumps(OrderedDict([("a", 1), ("b", 2)]))
'{"a": 1, "b": 2}'
>>> json.dumps(OrderedDict([("b", 2), ("a", 1)]))
'{"b": 2, "a": 1}'

Since Python 3.6, the keyword argument order is preserved and the above can be rewritten using a nicer syntax:

>>> json.dumps(OrderedDict(a=1, b=2))
'{"a": 1, "b": 2}'
>>> json.dumps(OrderedDict(b=2, a=1))
'{"b": 2, "a": 1}'

See PEP 468 – Preserving Keyword Argument Order.

If your input is given as JSON then to preserve the order (to get OrderedDict), you could pass object_pair_hook, as suggested by @Fred Yankowski:

>>> json.loads('{"a": 1, "b": 2}', object_pairs_hook=OrderedDict)
OrderedDict([('a', 1), ('b', 2)])
>>> json.loads('{"b": 2, "a": 1}', object_pairs_hook=OrderedDict)
OrderedDict([('b', 2), ('a', 1)])
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 4
    OrderedDict's init really ugly – jean Apr 10 '15 at 06:31
  • 3
    @jean: the initial value has nothing to do with `OrderedDict()`, you can pass a `dict` to `OrderedDict()`, you can pass a list of ordered pairs to `dict()` too -- though the order is lost in both of these cases. – jfs Apr 17 '15 at 10:31
  • I mean init it when preserve the order, needs typing many '(' and ')' – jean Apr 17 '15 at 11:19
  • @jean: there is [`ordereddict_literals` from `codetransformer` package](https://pypi.python.org/pypi/codetransformer#overloading-literals) (alpha quality) – jfs Feb 19 '16 at 18:27
  • 33
    Also, if you load JSON using `d = json.load(f, object_pairs_hook=OrderedDict)`, a later `json.dump(d)` will retain the order of the original elements. – Fred Yankowski May 26 '16 at 16:18
23

As others have mentioned the underlying dict is unordered. However there are OrderedDict objects in python. ( They're built in in recent pythons, or you can use this: http://code.activestate.com/recipes/576693/ ).

I believe that newer pythons json implementations correctly handle the built in OrderedDicts, but I'm not sure (and I don't have easy access to test).

Old pythons simplejson implementations dont handle the OrderedDict objects nicely .. and convert them to regular dicts before outputting them.. but you can overcome this by doing the following:

class OrderedJsonEncoder( simplejson.JSONEncoder ):
   def encode(self,o):
      if isinstance(o,OrderedDict.OrderedDict):
         return "{" + ",".join( [ self.encode(k)+":"+self.encode(v) for (k,v) in o.iteritems() ] ) + "}"
      else:
         return simplejson.JSONEncoder.encode(self, o)

now using this we get:

>>> import OrderedDict
>>> unordered={"id":123,"name":"a_name","timezone":"tz"}
>>> ordered = OrderedDict.OrderedDict( [("id",123), ("name","a_name"), ("timezone","tz")] )
>>> e = OrderedJsonEncoder()
>>> print e.encode( unordered )
{"timezone": "tz", "id": 123, "name": "a_name"}
>>> print e.encode( ordered )
{"id":123,"name":"a_name","timezone":"tz"}

Which is pretty much as desired.

Another alternative would be to specialise the encoder to directly use your row class, and then you'd not need any intermediate dict or UnorderedDict.

Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • 5
    Note that JSON objects are *unordered still*; a JSON client can read the object definition and completely ignore the order of the keys and be fully RFC compliant. – Martijn Pieters Aug 07 '13 at 14:46
  • 4
    Martijn is correct, this does not effect RFC compliance, but it can certainly still be valuable if you want to have a consistent format for your JSON (For example if the file is under version control, or to make it easier for a human reader to comprehend, of to make entry order match your documentation.) – Michael Anderson Aug 08 '13 at 01:02
  • 3
    In which case you just set `sort_keys` to `True` when calling `json.dumps()`; for order stability (for testing, stable caching or VCS commits), sorting keys is enough. – Martijn Pieters Feb 03 '14 at 09:44
14

hey i know it is so late for this answer but add sort_keys and assign false to it as follows :

json.dumps({'****': ***},sort_keys=False)

this worked for me

DanielM
  • 3,598
  • 5
  • 37
  • 53
8

The order of a dictionary doesn't have any relationship to the order it was defined in. This is true of all dictionaries, not just those turned into JSON.

>>> {"b": 1, "a": 2}
{'a': 2, 'b': 1}

Indeed, the dictionary was turned "upside down" before it even reached json.dumps:

>>> {"id":1,"name":"David","timezone":3}
{'timezone': 3, 'id': 1, 'name': 'David'}
David Robinson
  • 77,383
  • 16
  • 167
  • 187
8

json.dump() will preserve the ordder of your dictionary. Open the file in a text editor and you will see. It will preserve the order regardless of whether you send it an OrderedDict.

But json.load() will lose the order of the saved object unless you tell it to load into an OrderedDict(), which is done with the object_pairs_hook parameter as J.F.Sebastian instructed above.

It would otherwise lose the order because under usual operation, it loads the saved dictionary object into a regular dict and a regular dict does not preserve the oder of the items it is given.

markling
  • 1,232
  • 1
  • 15
  • 28
  • This is actually a better fix as maintaining the order on load takes care of dump time ordering. Thanks for this answer. – Arun R Sep 24 '18 at 20:39
2

Based on Michael Anderson's answer but also works when you pass in an array

class OrderedJsonEncoder(simplejson.JSONEncoder):
    def encode(self, o, first=True):
        if type(o) == list and first:
            return '[' + ",".join([self.encode(val, first=False) for val in o]) + ']'
        if type(o) == OrderedDict:
            return "{" + ",".join(
                [self.encode(k, first=False) + ":" + self.encode(v) for (k, v) in o.iteritems()]
            ) + "}"
        else:
            return simplejson.JSONEncoder.encode(self, o)
Daniel Kobe
  • 9,376
  • 15
  • 62
  • 109
0

Provided you're using Python 3.7+ , it does preserve the order.

Prior to Python 3.7, dict was not guaranteed to be ordered, so inputs and outputs were typically scrambled unless collections.OrderedDict was specifically requested. Starting with Python 3.7, the regular dict became order preserving, so it is no longer necessary to specify collections.OrderedDict for JSON generation and parsing.

https://docs.python.org/3/library/json.html#json.dump

alegria
  • 1,290
  • 14
  • 23
-2

Python 3.6.1:

Python 3.6.1 (default, Oct 10 2020, 20:16:48)
[GCC 7.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

>>> import json
>>> json.dumps({'b': 1, 'a': 2})
'{"b": 1, "a": 2}'

Python 2.7.5:

Python 2.7.5 (default, Nov 20 2015, 02:00:19) 
    [GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux2
    Type "help", "copyright", "credits" or "license" for more information.

>>> import json
>>> json.dumps({'b': 1, 'a': 2})
'{"a": 2, "b": 1}'
Lane Lee
  • 89
  • 2
  • 8