44

According to this conversion table, Python ints get written as JSON numbers when serialized using the JSON module--as I would expect and desire.

I have a dictionary with an integer key and integer value:

>>> d = {1:2}
>>> type(d.items()[0][0])
<type 'int'>
>>> type(d.items()[0][1])
<type 'int'>

When I use the json module to serialize this to a JSON string, the value is written as a number, but the key is written as a string:

>>> json.dumps(d)
'{"1": 2}'

This isn't the behavior I want, and it seems particularly broken since it breaks json.dumps/json.loads round-tripping:

>>> d == json.loads(json.dumps(d))
False

Why does this happen, and is there a way I can force the key to be written as a number?

jveldridge
  • 1,155
  • 3
  • 11
  • 21
  • 7
    JSON keys are *always* strings. – duffymo Jun 14 '13 at 00:59
  • 2
    ...and JSON uses strings because they're byte-order independent. – martineau Jun 14 '13 at 01:01
  • 5
    If JSON is not *strictly* required, you could go for YAML using PyYAML instead : `d = {1:2, 3:4}; import yaml; yaml.safe_load(yaml.safe_dump(d)) == d` returns True. In general the 'inline' style of YAML looks like more flexible json (keys can be numbers, strings usually don't have to be quoted). I use `safe_load` here because normal `yaml.load` has features (construction of classes,etc) that it's hard to secure correctly; `safe_load|dump` restrict the set of supported input/outputs to primitive types (bool,int,float,string,list,set,dict) and so are safe to use on arbitrary inputs. – kampu Jun 14 '13 at 02:15
  • Unfortunately the round tripping does not hold with yaml.safe_dump because it converts strings of things looking like ints into ints. E.g. try it with d = {'1':'2'} – Timothy Corbett-Clark Oct 24 '13 at 10:21
  • Bug for above round tripping issue: https://bitbucket.org/xi/pyyaml/issue/21 – Timothy Corbett-Clark Oct 24 '13 at 12:28
  • possible duplicate of [Python's json module, converts int dictionary keys to strings](http://stackoverflow.com/questions/1450957/pythons-json-module-converts-int-dictionary-keys-to-strings) – NoDataDumpNoContribution Nov 16 '14 at 21:47

3 Answers3

51

The simple reason is that JSON does not allow integer keys.

object
    {}
    { members } 
members
    pair
    pair , members
pair
    string : value  # Keys *must* be strings.

As to how to get around this limitation - you will first need to ensure that the receiving implementation can handle the technically-invalid JSON. Then you can either replace all of the quote marks or use a custom serializer.

Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
1

If you really want to, you can check keys for being convertable to integers again using:

def pythonify(json_data):
    for key, value in json_data.iteritems():
        if isinstance(value, list):
            value = [ pythonify(item) if isinstance(item, dict) else item for item in value ]
        elif isinstance(value, dict):
            value = pythonify(value)
        try:
            newkey = int(key)
            del json_data[key]
            key = newkey
        except TypeError:
            pass
        json_data[key] = value
    return json_data
JLT
  • 712
  • 9
  • 15
1

This function will recursively cast all string-keys to int-keys, if possible. If not possible the key-type will remain unchanged.

I adjusted JLT's example below slightly. With some of my huge nested dictionaries that code made the size of the dictionary change, ending with an exception. Anyhow, credit goes to JLT!

def pythonify(json_data):

    correctedDict = {}

    for key, value in json_data.items():
        if isinstance(value, list):
            value = [pythonify(item) if isinstance(item, dict) else item for item in value]
        elif isinstance(value, dict):
            value = pythonify(value)
        try:
            key = int(key)
        except Exception as ex:
            pass
        correctedDict[key] = value

    return correctedDict