63
votes

Is there a way in Python to serialize a dictionary that is using a tuple as key:

a={(1,2):'a'}

simply using json.dumps(a), produces:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/json/__init__.py", line 230, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib/python2.6/json/encoder.py", line 367, in encode
    chunks = list(self.iterencode(o))
  File "/usr/lib/python2.6/json/encoder.py", line 309, in _iterencode
    for chunk in self._iterencode_dict(o, markers):
  File "/usr/lib/python2.6/json/encoder.py", line 268, in _iterencode_dict
    raise TypeError("key {0!r} is not a string".format(key))
TypeError: key (1, 2) is not a string
7
possible duplicate of Best way to encode tuples with json - agf

7 Answers

37
votes

You can't serialize that as json, json has a much less flexible idea about what counts as a dict key than python.

You could transform the mapping into a sequence of key, value pairs, something like this:

>>> import json
>>> def remap_keys(mapping):
...     return [{'key':k, 'value': v} for k, v in mapping.iteritems()]
... 
>>> json.dumps(remap_keys({(1, 2): 'foo'}))
'[{"value": "foo", "key": [1, 2]}]'
9
votes

JSON only supports strings as keys. You'll need to choose a way to represent those tuples as strings.

8
votes

You could just use str((1,2)) as key because json only expects the keys as strings but if you use this you'll have to use a[str((1,2))] to get the value.

8
votes
from json import load, dump
from ast import literal_eval

x={ (0,1):'la-la la', (0,2):'extricate' }

# save: convert each tuple key to a string before saving as json object
with open('/tmp/test', 'w') as f: dump({str(k):v for k, v in x.items()}, f)

# load in two stages:#
# (i) load json object
with open('/tmp/test', 'r') as f: obj = load(f)

# (ii) convert loaded keys from string back to tuple
d={literal_eval(k):v for k, v in obj.items()}

See: https://stackoverflow.com/a/12337657/2455413

3
votes

json can only accept strings as keys for dict, what you can do, is to replace the tuple keys with string like so

with open("file", "w") as f:
    k = dic.keys() 
    v = dic.values() 
    k1 = [str(i) for i in k]
    json.dump(json.dumps(dict(zip(*[k1,v]))),f) 

And than when you want to read it, you can change the keys back to tuples using

with open("file", r) as f:
    data = json.load(f)
    dic = json.loads(data)
    k = dic.keys() 
    v = dic.values() 
    k1 = [eval(i) for i in k] 
    return dict(zip(*[k1,v])) 
2
votes

Here is one way to do it. It will require the key to be json decoded after the main dictionary is decoded and the whole dictionary re-sequenced, but it is doable:

    import json

    def jsonEncodeTupleKeyDict(data):
        ndict = dict()
        # creates new dictionary with the original tuple converted to json string
        for key,value in data.iteritems():
            nkey = json.dumps(key)
            ndict[nkey] =  value

        # now encode the new dictionary and return that
        return json.dumps(ndict)

    def main():
        tdict = dict()
        for i in range(10):
            key = (i,"data",5*i)
            tdict[key] = i*i

        try:
            print json.dumps(tdict)
        except TypeError,e:
            print "JSON Encode Failed!",e

        print jsonEncodeTupleKeyDict(tdict)

    if __name__ == '__main__':
        main()

I make no claim to any efficiency of this method. I needed this for saving some joystick mapping data to a file. I wanted to use something that would create a semi-human readable format so it could be edited if needed.

0
votes

You can actually not serialize tuples as key to json, but you can convert the tuple to a string and recover it, after you have deserialized the file.

with_tuple = {(0.1, 0.1): 3.14} ## this will work in python but is not serializable in json
{(0.1, 0.1): 3.14}

But you cannot serialize it with json. However, you can use

with_string = {str((0.1, 0.1))[1:-1]: 3.14} ## te expression [1,-1] removes the parenthesis surronding the tuples in pyhton. 
{'0.1, 0.1': 3.14} # This is serializable

With a bit of cheating, you will recover the original tuple (after having deserialized the whole file) by treating each key (as str) separately

tuple(json.loads("["+'0.1, 0.1'+"]")) ## will recover the tuple from string
(0.1, 0.1)

It is a bit of overload to convert a string to a tuple using json.loads, but it will work. Encapsulate it and you are done.

Peace out and happy coding!

Nicolas