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
('category1', 'category2')- heck, it doesn't have tuples at all. You'll have to reorganize your data.