14

I have some very simple code that takes a dictionary with a tuple as a key and turns it into json:

In [11]: import simplejson as json In [12]: data = {('category1', 'category2'): 4} In [13]: json.dumps(data) 

However, running the code gives me:

TypeError: keys must be a string 

I have tried str()'ing the keys and everything else I can find, but without luck.

2
  • 5
    Do you know the JSON format? It doesn't allow keys like ('category1', 'category2') - heck, it doesn't have tuples at all. You'll have to reorganize your data. Commented Jun 3, 2015 at 19:57
  • You may hack it by creating string from the tuple like {"category1#category2":4}. JSON itself doesn't work with tuples as keys. Commented Jun 3, 2015 at 20:22

5 Answers 5

9

The error seems pretty clear: Your keys must be strings.

If I take the example from your question and str()-ify the keys:

>>> data = {str(('category1', 'category2')): 4} 

It works just fine:

>>> json.dumps(data) '{"(\'category1\', \'category2\')": 4}' 

Having said that, in your position I would consider making your keys more readable. Possibly something like:

>>> data = dict((':'.join(k), v) for k,v in data.items()) 

This turns a key like ('category1', 'category2') into category1:category2,

Sign up to request clarification or add additional context in comments.

2 Comments

It would be nice if this answer could explain how to restore the original object. json serialization is a two-way operation... It is not enough to do dumps, one also has to know how to loads.
I'm not sure: the question isn't really clear about what data structure is actually required. Is the string-ified key sufficient? Then one can simply call loads on the JSON data and be done with it. If the data structure requires tuples as key, then the solution is going to look different. That said, if you have particular questions around de-serializing JSON, feel free to open a new question and we'd be happy to help out.
6

I know that The Zen of Python states

Flat is better than nested.

However, for my context, I had some levels of nested dicts. So I recursively treated it:

import json def key_to_json(data): if data is None or isinstance(data, (bool, int, str)): return data if isinstance(data, (tuple, frozenset)): return str(data) raise TypeError def to_json(data): if data is None or isinstance(data, (bool, int, tuple, range, str, list)): return data if isinstance(data, (set, frozenset)): return sorted(data) if isinstance(data, dict): return {key_to_json(key): to_json(data[key]) for key in data} raise TypeError data = {('category1', 'category2'): {frozenset(['cat1', 'cat2']): 1212}} json.dumps(to_json(data)) # '{"(\'category1\', \'category2\')": {"frozenset({\'cat2\', \'cat1\'})": 1212}}' 

Adjust this code to match your context.

Comments

3

Please try the below approach. Make the key of your dictionary a string rather than tuple before conversion to json. When converting json back to dictionary, convert back into list/tuple.

data =['category1', 'category2'] data_map = {} data_map[','.join(data)]= 4 json.dumps(data_map) 

Do not forget to convert back the string to list once you convert read the json to dictionary

1 Comment

How to convert string to tuple when we read the json file?
2

As @starriet said, if your goal is to serialize and then deserialize without data loss then you should look at pickle.

However, if you just wanna print things for debugging/human readability then checkout the stringify method:

import json import numpy as np from collections.abc import Iterable encode = json.JSONEncoder().encode def cast_key(key): "Cast dictionary key" try: encode({key: 123}) return key except: pass if isinstance(key, tuple): return encode(key) return str(key) def cast_value(value): "Cast dictionary value" try: encode(value) return value except TypeError: pass try: if np.issubdtype(value, np.integer): return int(value) except ValueError: pass return str(value) def coerce_types(arg): if isinstance(arg, dict): obj = {} for k, v in arg.items(): k = cast_key(k) if isinstance(v, dict): v = coerce_types(v) else: v = cast_value(v) obj[k] = v return obj elif isinstance(arg, Iterable): return [coerce_types(e) for e in arg] else: return cast_value(arg) def stringify(obj): # First try default serializer try: return json.dumps(obj) except TypeError: pass # Default failed, so we coerce types and try again obj_prep = coerce_types(obj) return json.dumps(obj_prep) 

PyTest

import numpy as np from stringify import stringify def test_simple_object(): input = {'foo': 'bar'} expected = '{"foo": "bar"}' actual = stringify(input) assert expected == actual def test_nested_object(): input = {'foo': {'child': 'bar'}, 'age': 20} expected = '{"foo": {"child": "bar"}, "age": 20}' actual = stringify(input) assert expected == actual def test_numpy_value_int(): input = {'foo': np.int64(123)} expected = '{"foo": 123}' actual = stringify(input) assert expected == actual def test_numpy_value_float(): input = {'foo': np.float64(123.456)} expected = '{"foo": 123.456}' actual = stringify(input) assert expected == actual def test_numpy_key(): input = {np.int64(123): 'foo'} expected = '{"123": "foo"}' actual = stringify(input) assert expected == actual def test_numpy_nested_l1(): input = {'foo': {'bar': {np.int64(123): 'foo'}}, 'age': 20} expected = '{"foo": {"bar": {"123": "foo"}}, "age": 20}' actual = stringify(input) assert expected == actual def test_numpy_nested_l2(): input = {'foo': {'bar': {'baz': {'foo': np.int64(123)}}}, 'age': 20} expected = '{"foo": {"bar": {"baz": {"foo": 123}}}, "age": 20}' actual = stringify(input) assert expected == actual def test_array_int(): input = [1, 2, 3] expected = '[1, 2, 3]' actual = stringify(input) assert expected == actual def test_array_numpy_int(): input = [np.int64(n) for n in [1, 2, 3]] expected = '[1, 2, 3]' actual = stringify(input) assert expected == actual def test_array_numpy_float(): input = [np.float64(n) for n in [1.1, 2.2, 3.3]] expected = '[1.1, 2.2, 3.3]' actual = stringify(input) assert expected == actual def test_object_array(): input = [{'foo': 'bar'}] expected = '[{"foo": "bar"}]' actual = stringify(input) assert expected == actual def test_object_array_numpy(): input = [{'foo': 'bar'}, {'bar': np.int64(123)}] expected = '[{"foo": "bar"}, {"bar": 123}]' actual = stringify(input) assert expected == actual def test_tuple_value(): input = {'foo': ('bar', 'baz')} expected = '{"foo": ["bar", "baz"]}' actual = stringify(input) assert expected == actual def test_tuple_key(): input = {('bar', 'baz'): 'foo'} expected = '{"[\\"bar\\", \\"baz\\"]": "foo"}' actual = stringify(input) assert expected == actual 

Comments

1

If your purpose is to save, just use pickle.

the API is almost the same.

import pickle data = {('category1', 'category2'): 4} s = pickle.dumps(data) # serialized data d = pickle.loads(s) # the original dictionary 

If you wanna save it into disk,

# write with open('file1.pkl', 'wb') as f: pickle.dump(data, f) # read with open('file1.pkl', 'rb') as f: data = pickle.load(f) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.