1131

How can I convert the str representation of a dict, such as the following string, into a dict?

s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"

I prefer not to use eval. What else can I use?

The main reason for this, is one of my coworkers classes he wrote, converts all input into strings. I'm not in the mood to go and modify his classes, to deal with this issue.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
UberJumper
  • 20,245
  • 19
  • 69
  • 87
  • 26
    **Note**: For those that come here with *deceptively similar looking* **JSON** data, you want to go read [Parse JSON in Python](//stackoverflow.com/q/7771011) instead. JSON is *not the same thing as Python*. If you have `"` double quotes around your strings you probably have JSON data. You can also look for `null`, `true` or `false`, Python syntax uses `None`, `True` and `False`. – Martijn Pieters Nov 22 '18 at 13:23
  • 2
    If you can't use Python 2.6, you can use a simple safeeval implmenentation like http://code.activestate.com/recipes/364469/ It piggybacks on the Python compiler so you don't have to do all the gross work yourself. – Ned Batchelder Jun 12 '09 at 19:09

12 Answers12

1656

You can use the built-in ast.literal_eval:

>>> import ast
>>> ast.literal_eval("{'muffin' : 'lolz', 'foo' : 'kitty'}")
{'muffin': 'lolz', 'foo': 'kitty'}

This is safer than using eval. As its own docs say:

>>> help(ast.literal_eval)
Help on function literal_eval in module ast:

literal_eval(node_or_string)
    Safely evaluate an expression node or a string containing a Python
    expression.  The string or node provided may only consist of the following
    Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
    and None.

For example:

>>> eval("shutil.rmtree('mongo')")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
  File "/opt/Python-2.6.1/lib/python2.6/shutil.py", line 208, in rmtree
    onerror(os.listdir, path, sys.exc_info())
  File "/opt/Python-2.6.1/lib/python2.6/shutil.py", line 206, in rmtree
    names = os.listdir(path)
OSError: [Errno 2] No such file or directory: 'mongo'
>>> ast.literal_eval("shutil.rmtree('mongo')")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/Python-2.6.1/lib/python2.6/ast.py", line 68, in literal_eval
    return _convert(node_or_string)
  File "/opt/Python-2.6.1/lib/python2.6/ast.py", line 67, in _convert
    raise ValueError('malformed string')
ValueError: malformed string
Asocia
  • 5,935
  • 2
  • 21
  • 46
Jacob Gabrielson
  • 34,800
  • 15
  • 46
  • 64
  • 1
    I should add that you need to sanitize the string for use with ast.literal_eval. (ensure quotes/double quotes in string are escaped) – Paulo Matos Oct 04 '12 at 10:10
  • i get this error I am on python 2.6 (x86) on windows 7 x64 File "D:\Python26\lib\ast.py", line 48, in literal_eval node_or_string = parse(node_or_string, mode='eval') File "D:\Python26\lib\ast.py", line 36, in parse return compile(expr, filename, mode, PyCF_ONLY_AST) File "", line 1 ^ SyntaxError: invalid syntax –  Dec 10 '12 at 07:38
  • 1
    what about `"dict(a=1)"` style strings? – n611x007 Jul 04 '14 at 11:44
  • This doesn't seem to work for enum value inside a dictionary. Eg: d = "{'col': , 'val': 2}" – shivshnkr Jan 04 '17 at 14:45
  • 4
    why don't use json.dumps and json.loads insead, I found this solution more elevant thant using eval – JuanB Jan 07 '18 at 16:23
  • @n611x007 that's a statement, so it won't be allowed – Trenton Nov 05 '18 at 21:09
  • I still don't see why it's better than eval(). Is it because it only accepts the structures given? – KDM Nov 17 '18 at 16:37
  • I have a naive question. Worried that I may have misunderstood "import". Responder says ast is built-in. Why do I need to import it? – KDM Nov 17 '18 at 16:39
  • 5
    @JuanB json.loads doesn't accept [single-quoted strings](https://stackoverflow.com/a/50257217/8857601). It also doesn't accept None, only null. – Michael Wheeler Apr 22 '21 at 14:55
  • That nice. My string dict actually included dict inside and method not working. – ati ince Sep 15 '21 at 14:35
395

https://docs.python.org/library/json.html

JSON can solve this problem, though its decoder wants double quotes around keys and values. If you don't mind a replace hack...

import json
s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"
json_acceptable_string = s.replace("'", "\"")
d = json.loads(json_acceptable_string)
# d = {u'muffin': u'lolz', u'foo': u'kitty'}

NOTE that if you have single quotes as a part of your keys or values this will fail due to improper character replacement. This solution is only recommended if you have a strong aversion to the eval solution.

More about json single quote: jQuery.parseJSON throws “Invalid JSON” error due to escaped single quote in JSON

wjandrea
  • 28,235
  • 9
  • 60
  • 81
i0x539
  • 4,763
  • 2
  • 20
  • 28
  • 5
    Another problem is for `"{0: 'Hello'}"`. – Finn Årup Nielsen Aug 23 '17 at 12:14
  • 5
    This also fails if you have trailing commas (not JSON compliant), eg: "{'muffin' : 'lolz', 'foo' : 'kitty',}" – gdvalderrama Mar 21 '18 at 16:35
  • 2
    Single-quoted strings, tuple literals, and trailing commas are not valid JSON. `json.loads` will only work on a valid JSON string. See the spec here: https://www.json.org/ Using `json.loads` is the safest solution, so use if possible. I would recommend transforming your input into valid JSON if necessary. – AuthorOfTheSurf May 22 '18 at 19:31
  • 1
    Also this solution does not work if you have unicode strings – Matias Gonzalez Sep 20 '18 at 13:06
  • I had a similar question here https://stackoverflow.com/questions/58561257/how-to-convert-string-without-quotes-and-with-arrays-and-sub-dictionary-to-a-dic but without quotes – Eric Bellet Oct 25 '19 at 15:36
  • 5
    Not working if value `None` (without single quotes, so can' t replace), e.g. `"{'d': None}"` – 林果皞 Aug 09 '20 at 01:33
  • 1
    If your dict structure includes inside another dict etc ..... not working. much usuful for general needs. – ati ince Sep 15 '21 at 14:37
  • 1
    Save my life for **handling a dict posted by a Django form**. In this case, the form is posted as a string . Thanks – Abpostman1 May 15 '23 at 17:38
232

using json.loads:

>>> import json
>>> h = '{"foo":"bar", "foo2":"bar2"}'
>>> d = json.loads(h)
>>> d
{u'foo': u'bar', u'foo2': u'bar2'}
>>> type(d)
<type 'dict'>
tokhi
  • 21,044
  • 23
  • 95
  • 105
  • 25
    I dont think it answers the OP's answer. How do we use json.laads to convert a string s = "{'muffin' : 'lolz', 'foo' : 'kitty'}" to dict? – technazi May 13 '16 at 14:28
  • why is this printing 'u' in the output?? eg - str = '{"1":"P", "2":"N", "3":"M"}' d = json.loads(str) print d output is : {u'1': u'P', u'3': u'M', u'2': u'N'} – user905 Jul 13 '16 at 18:24
  • 4
    @technazi: json.loads(h.replace("'",'"')) – ntg Dec 08 '16 at 15:08
  • 1
    However, there are limits, e.g.: h= '{"muffin" : "lolz", "foo" : "kitty",}', also h= '{"muffin's" : "lolz", "foo" : "kitty"}', (just noticed part of the same comments in a similar answer... still leaving here for completeness...) – ntg Dec 08 '16 at 15:29
  • 8
    In my opinion, that's the shortest and easiest way... Definitely the one I personally prefer. – nostradamus Jan 11 '17 at 08:40
  • 3
    @nostradamus Too many exceptions, floating point values, tuples, etc. etc. – MrR Feb 23 '19 at 00:04
  • 1
    Thx, this seems to be the most concise way. Definitely using it from now on. But gotta keep track of trailing commas and shielded quotations – lotrus28 Oct 09 '19 at 13:36
  • This is the best way if the user knows that the `string` is a `json`. The clue to this is the fact that the format has double quotes. `{"key" : "value"}` – D.L Jun 10 '21 at 00:58
54

To OP's example:

s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"

We can use Yaml to deal with this kind of non-standard json in string:

>>> import yaml
>>> s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"
>>> s
"{'muffin' : 'lolz', 'foo' : 'kitty'}"
>>> yaml.load(s)
{'muffin': 'lolz', 'foo': 'kitty'}
lqhcpsgbl
  • 3,694
  • 3
  • 21
  • 30
  • 8
    This will cause 'yes' and 'no' strings to be converted to True / False – Eric Marcos Aug 18 '17 at 17:57
  • i got my value that works fine....but i get a error with it "AMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details." what is it?? – shailu May 02 '21 at 17:13
  • 1
    Only use this yaml parser for *trusted* input. Preferably use `safe_load` to avoid security implications. – Yasin Zähringer Jul 23 '21 at 13:44
  • config = yaml.load(ymlfile, Loader=yaml.Loader) – avadhut007 Nov 25 '22 at 06:18
33

To summarize:

import ast, yaml, json, timeit

descs=['short string','long string']
strings=['{"809001":2,"848545":2,"565828":1}','{"2979":1,"30581":1,"7296":1,"127256":1,"18803":2,"41619":1,"41312":1,"16837":1,"7253":1,"70075":1,"3453":1,"4126":1,"23599":1,"11465":3,"19172":1,"4019":1,"4775":1,"64225":1,"3235":2,"15593":1,"7528":1,"176840":1,"40022":1,"152854":1,"9878":1,"16156":1,"6512":1,"4138":1,"11090":1,"12259":1,"4934":1,"65581":1,"9747":2,"18290":1,"107981":1,"459762":1,"23177":1,"23246":1,"3591":1,"3671":1,"5767":1,"3930":1,"89507":2,"19293":1,"92797":1,"32444":2,"70089":1,"46549":1,"30988":1,"4613":1,"14042":1,"26298":1,"222972":1,"2982":1,"3932":1,"11134":1,"3084":1,"6516":1,"486617":1,"14475":2,"2127":1,"51359":1,"2662":1,"4121":1,"53848":2,"552967":1,"204081":1,"5675":2,"32433":1,"92448":1}']
funcs=[json.loads,eval,ast.literal_eval,yaml.load]

for  desc,string in zip(descs,strings):
    print('***',desc,'***')
    print('')
    for  func in funcs:
        print(func.__module__+' '+func.__name__+':')
        %timeit func(string)        
    print('')

Results:

*** short string ***

json loads:
4.47 µs ± 33.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
builtins eval:
24.1 µs ± 163 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
ast literal_eval:
30.4 µs ± 299 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
yaml load:
504 µs ± 1.29 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

*** long string ***

json loads:
29.6 µs ± 230 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
builtins eval:
219 µs ± 3.92 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
ast literal_eval:
331 µs ± 1.89 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
yaml load:
9.02 ms ± 92.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Conclusion: prefer json.loads

Anatoly Alekseev
  • 2,011
  • 24
  • 27
  • 12
    Except this won't work with his single-quoted string, which was part of his initial problem. Performance was never mentioned. – Michael Campbell Nov 26 '18 at 16:49
  • 3
    +1 for the benchmarks (it helps making an informed decision), -1 for the conclusion: as mentioned many times, `json` fails in many cases. Should be up to the user to choose between features vs performance. – MestreLion Apr 19 '21 at 12:27
25

If the string can always be trusted, you could use eval (or use literal_eval as suggested; it's safe no matter what the string is.) Otherwise you need a parser. A JSON parser (such as simplejson) would work if he only ever stores content that fits with the JSON scheme.

Blixt
  • 49,547
  • 13
  • 120
  • 153
  • 9
    Starting in 2.6, simplejson is included in the Python standard library as the json module. – Eli Courtwright Jun 12 '09 at 18:37
  • 12
    Yeah, that's a good answer, but note that officially JSON doesn't support single-quoted strings, as given in the original poster's example. – Ben Hoyt Jun 14 '09 at 21:30
20

Use json. the ast library consumes a lot of memory and and slower. I have a process that needs to read a text file of 156Mb. Ast with 5 minutes delay for the conversion dictionary json and 1 minutes using 60% less memory!

Xantium
  • 11,201
  • 10
  • 62
  • 89
11
string = "{'server1':'value','server2':'value'}"

#Now removing { and }
s = string.replace("{" ,"")
finalstring = s.replace("}" , "")

#Splitting the string based on , we get key value pairs
list = finalstring.split(",")

dictionary ={}
for i in list:
    #Get Key Value pairs separately to store in dictionary
    keyvalue = i.split(":")

    #Replacing the single quotes in the leading.
    m= keyvalue[0].strip('\'')
    m = m.replace("\"", "")
    dictionary[m] = keyvalue[1].strip('"\'')

print dictionary
Community
  • 1
  • 1
  • 3
    Many mistakes in this approach. What if value of a key contains `{` or `}`. What if it is nested `dict`. What if value contains `,` ?? – Om Sao Mar 29 '19 at 10:28
7

Optimized code of Siva Kameswara Rao Munipalle

s = s.replace("{", "").replace("}", "").split(",")
            
dictionary = {}

for i in s:
    dictionary[i.split(":")[0].strip('\'').replace("\"", "")] = i.split(":")[1].strip('"\'')
            
print(dictionary)
tsuresh97
  • 496
  • 5
  • 12
6

no any libs are used (python2):

dict_format_string = "{'1':'one', '2' : 'two'}"
d = {}
elems  = filter(str.isalnum,dict_format_string.split("'"))
values = elems[1::2]
keys   = elems[0::2]
d.update(zip(keys,values))

NOTE: As it has hardcoded split("'") will work only for strings where data is "single quoted".

NOTE2: In python3 you need to wrap filter() to list() to get list.

tamerlaha
  • 1,902
  • 1
  • 17
  • 25
  • elems = filter(str.isalnum,dict_format_string.split("'")) should be list(elems = filter(str.isalnum,dict_format_string.split("'"))) without converting to list it would still be 'filter' object – Aravind Krishnakumar Jan 23 '21 at 22:08
1

I couldn't use any of the above answers cause I had a string with the dtypes specified, so I used the json.load as follow:

string_dict = """{'contexts': array(['Programmed cell death (PCD) is the regulated death of cells within an organism.', ..., '...'], dtype=object)},..."""

# Replace array( with np.array(
string_dict = string_dict.replace("array(", "np.array(")

# Evaluate the string as python code
python_dict = ast.literal_eval(string_dict)
0

My string didn't have quotes inside:
s = 'Date: 2022-11-29T10:57:01.024Z, Size: 910.11 KB'

My solution was to use str.split:
{k:v for k, v in map(lambda d: d.split(': '), s.split(', '))}

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
A. West
  • 571
  • 5
  • 12
  • @EricAya, why did you edit my question out? How can I do this without a `map`? – A. West Nov 29 '22 at 18:17
  • Because the answers section is just for answers, not for questions, this is not a discussion forum. If you have a new question you need to post an actual new question. – Eric Aya Nov 29 '22 at 22:26