82

I'm receiving a dict from one "layer" of code upon which some calculations/modifications are performed before passing it onto another "layer". The original dict's keys & "string" values are unicode, but the layer they're being passed onto only accepts str.

This is going to be called often, so I'd like to know what would be the fastest way to convert something like:

{ u'spam': u'eggs', u'foo': True, u'bar': { u'baz': 97 } }

...to:

{ 'spam': 'eggs', 'foo': True, 'bar': { 'baz': 97 } }

...bearing in mind the non-"string" values need to stay as their original type.

Any thoughts?

Phillip B Oldham
  • 18,807
  • 20
  • 94
  • 134

8 Answers8

156
DATA = { u'spam': u'eggs', u'foo': frozenset([u'Gah!']), u'bar': { u'baz': 97 },
         u'list': [u'list', (True, u'Maybe'), set([u'and', u'a', u'set', 1])]}

def convert(data):
    if isinstance(data, basestring):
        return str(data)
    elif isinstance(data, collections.Mapping):
        return dict(map(convert, data.iteritems()))
    elif isinstance(data, collections.Iterable):
        return type(data)(map(convert, data))
    else:
        return data

print DATA
print convert(DATA)
# Prints:
# {u'list': [u'list', (True, u'Maybe'), set([u'and', u'a', u'set', 1])], u'foo': frozenset([u'Gah!']), u'bar': {u'baz': 97}, u'spam': u'eggs'}
# {'bar': {'baz': 97}, 'foo': frozenset(['Gah!']), 'list': ['list', (True, 'Maybe'), set(['and', 'a', 'set', 1])], 'spam': 'eggs'}

Assumptions:

  • You've imported the collections module and can make use of the abstract base classes it provides
  • You're happy to convert using the default encoding (use data.encode('utf-8') rather than str(data) if you need an explicit encoding).

If you need to support other container types, hopefully it's obvious how to follow the pattern and add cases for them.

RichieHindle
  • 272,464
  • 47
  • 358
  • 399
25

I know I'm late on this one:

def convert_keys_to_string(dictionary):
    """Recursively converts dictionary keys to strings."""
    if not isinstance(dictionary, dict):
        return dictionary
    return dict((str(k), convert_keys_to_string(v)) 
        for k, v in dictionary.items())
Germano
  • 2,452
  • 18
  • 25
  • 1
    Yup, this seems like the correct way of doing it, the inline and other versions really aren't enough for real world scenarios. Too bad there is no reliable inline recursionless way to accomplish this. Or maybe there is based on python str(...) json conventions? – jayunit100 Sep 15 '12 at 00:23
  • 1
    This is my favorite, to only convert the keys, which is what I was looking for. Small typo: you need an additional () around the dict() argument that is returned. – ggll Jul 30 '13 at 12:45
  • The only problem with this solution is if your keys are NOT all strings (i.e. int type) – MrWonderful Mar 14 '16 at 19:11
  • @MrWonderful and why is that? I can't see any problem in calling ``str`` on an int – Germano Mar 15 '16 at 09:29
  • @Germano : Of course you can call str() on an int, but you get a str.... not an int anymore. So the type of the key would be changed from an int to a str, which is *more* than changing unicode to str -- the original question. – MrWonderful Mar 15 '16 at 23:56
  • @MrWonderful I agree about the original question. The conversion to a string of an int is perfectly consistent with the name of the method though ;) – Germano Mar 16 '16 at 15:57
  • This only does keys. – Scott Tesler Jul 31 '17 at 22:01
  • This fails to handle other nested iterable types. – BuvinJ May 30 '18 at 13:55
12

If you wanted to do this inline and didn't need recursive descent, this might work:

DATA = { u'spam': u'eggs', u'foo': True, u'bar': { u'baz': 97 } }
print DATA
# "{ u'spam': u'eggs', u'foo': True, u'bar': { u'baz': 97 } }"

STRING_DATA = dict([(str(k), v) for k, v in data.items()])
print STRING_DATA
# "{ 'spam': 'eggs', 'foo': True, 'bar': { u'baz': 97 } }"
Samuel Clay
  • 1,252
  • 2
  • 13
  • 24
  • 4
    For 2.7 and onwards this can be simplified as follows: `{ str(key):value for key,value in data.items() }` – AnjoMan May 13 '15 at 14:49
4

for a non-nested dict (since the title does not mention that case, it might be interesting for other people)

{str(k): str(v) for k, v in my_dict.items()}
maxbellec
  • 16,093
  • 10
  • 36
  • 43
3
def to_str(key, value):
    if isinstance(key, unicode):
        key = str(key)
    if isinstance(value, unicode):
        value = str(value)
    return key, value

pass key and value to it, and add recursion to your code to account for inner dictionary.

SilentGhost
  • 307,395
  • 66
  • 306
  • 293
2

To make it all inline (non-recursive):

{str(k):(str(v) if isinstance(v, unicode) else v) for k,v in my_dict.items()}
Ben
  • 21
  • 1
1
>>> d = {u"a": u"b", u"c": u"d"}
>>> d
{u'a': u'b', u'c': u'd'}
>>> import json
>>> import yaml
>>> d = {u"a": u"b", u"c": u"d"}
>>> yaml.safe_load(json.dumps(d))
{'a': 'b', 'c': 'd'}
lyu.l
  • 282
  • 3
  • 8
0

Just use print(*(dict.keys()))

The * can be used for unpacking containers e.g. lists. For more info on * check this SO answer.

Coddy
  • 549
  • 4
  • 18
  • Although this code might solve the problem, a good answer should explain **what** the code does and **how** it helps. – BDL May 19 '20 at 15:54