9

It is well known that json converts integer keys of a dict to string:

import json
print json.dumps({1: [2.5, 2.5, 2.5], 2: [3, 3, 3, 3]})
# {"1": [2.5, 2.5, 2.5], "2": [3, 3, 3, 3]}

What's the cleanest way to restore integer keys when loading it back?

d = json.loads('{"1": [2.5, 2.5, 2.5], "2": [3, 3, 3, 3]}')
print d
# {u'1': [2.5, 2.5, 2.5], u'2': [3, 3, 3, 3]}

I was thinking about:

d = {int(k): d[k] for k in d}

but is there a cleaner way to deal with dictionaries with integer keys with JSON / Python, not requiring keys conversion a posteriori?

Basj
  • 41,386
  • 99
  • 383
  • 673
  • 1
    Cleaner in what way? You could do `{int(k):v for k,v in d.items()}` – juanpa.arrivillaga Oct 29 '18 at 17:03
  • @juanpa.arrivillaga To avoid conversion, and have it directly loaded back as a dict with integer keys. – Basj Oct 29 '18 at 17:04
  • Well, since JSON requires string keys, you'll either have to write your own decoder (which is straightforward with the `json` library) or just convert, or chose an alternative serialization format. – juanpa.arrivillaga Oct 29 '18 at 17:05
  • @juanpa.arrivillaga Hum, ok, I think this is the answer (if you want to post it). – Basj Oct 29 '18 at 17:09

2 Answers2

17

Use object_hook to define a custom function and perform operations:

import json

def keystoint(x):
    return {int(k): v for k, v in x.items()}

j = json.dumps({1: [2.5, 2.5, 2.5], 2: [3, 3, 3, 3]})
# {"1": [2.5, 2.5, 2.5], "2": [3, 3, 3, 3]}

print(json.loads(j, object_hook=keystoint))
# {1: [2.5, 2.5, 2.5], 2: [3, 3, 3, 3]}

From docs:

object_hook is an optional function that will be called with the result of any object literal decoded (a dict). The return value of object_hook will be used instead of the dict.


Or, you could also use object_pairs_hook that lets you iterate through pairs and saves the .items() call (Thanks @chepner):
import json

def keystoint(x):
    return {int(k): v for k, v in x}

j = json.dumps({1: [2.5, 2.5, 2.5], 2: [3, 3, 3, 3]})
# {"1": [2.5, 2.5, 2.5], "2": [3, 3, 3, 3]}

print(json.loads(j, object_pairs_hook=keystoint))
# {1: [2.5, 2.5, 2.5], 2: [3, 3, 3, 3]}

From docs:

object_pairs_hook is an optional function that will be called with the result of any object literal decoded with an ordered list of pairs. The return value of object_pairs_hook will be used instead of the dict.

Austin
  • 25,759
  • 4
  • 25
  • 48
  • 2
    `object_pairs_hook` is slightly simpler (saving a call to the `items` method), as it receives a list of key/value pairs instead of a dict. `loads(j, object_pairs_hook=lambda pairs: {int(k): v for k,v in pairs})`. – chepner Oct 29 '18 at 17:18
  • @chepner, nice point. Of course, if the size is larger, saving `.items()` call should matter. – Austin Oct 29 '18 at 17:29
  • The default decoder, at least, always builds a concrete list of key/value pairs in memory. `object_pairs_hook` short-circuits the normal process by operating directly on that list; otherwise, the list is first turned into a `dict`, which is either returned as-is or passed to `object_hook` (That is, given `pairs`, the decoder returns `object_pairs_hook(pairs)`, `dict(pairs)`, or `object_hook(dict(pairs))`.) – chepner Oct 29 '18 at 17:39
  • Doesn't work for recursive string keys. – Sai Nikhil Dec 15 '22 at 08:30
4

I also want to add another solution if you would have a nested dictionary.


I've seen this solution under another question which handles ordinary dictionaries and also nested dictionaries which gives error if you have non-integer keys in the second layers.
>>> import json
>>> json_data = '{"1": "one", "2": {"-3": "minus three", "4": "four"}}'
>>> py_dict = json.loads(json_data, object_hook=lambda d: {int(k) 
                         if k.lstrip('-').isdigit() else k: v for k, v in d.items()})
>>> py_dict
{1: 'one', 2: {-3: 'minus three', 4: 'four'}}

As we can see that object_hook is used to solve the problem, and lstrip('-') is used to handle negative values, too.

Isa Rota
  • 433
  • 4
  • 8