How to check if a string contains a dictionary
<p>I want to recursively parse all values in a dict that are strings with <code>ast.literal_eval(value)</code> but not do that eval if the string doesn't contain a dict. I want this, because I have a string in a dict that is a dict in itself and I would like the value to be a dict. Best to give an example</p>

<pre><code>my_dict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': 'another string'}"}
</code></pre>

<p>Now I don't want a do to <code>ast.literal_eval(my_dict['c'])</code> I want a generic solution where I can do <code>convert_to_dict(my_dict)</code></p>

<p>I wanted to write my own method, but I don't know how to check if a string contains a dict, and then ast.literal_eval will fail, hence the question.</p>

<p>You can check if you have a dict after using <em>literal_eval</em> and reassign:</p>

<pre><code>from ast import literal_eval

def reassign(d):
    for k, v in d.items():
        try:
            evald = literal_eval(v)
            if isinstance(evald, dict):
                d[k] = evald
        except ValueError:
            pass
</code></pre>

<p>Just pass in the dict:</p>

<pre><code>In [2]: my_dict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': 'another stri
   ...: ng'}"}

In [3]: reassign(my_dict)

In [4]: my_dict
Out[4]: {'a': 42, 'b': 'my_string', 'c': {'d': 33, 'e': 'another string'}}

In [5]: my_dict = {'a': '42', 'b': "my_string", '5': "{'d': 33, 'e': 'another st
...: ring', 'other_dict':{'foo':'bar'}}"}
In [6]: reassign(my_dict)  
In [7]: my_dict
Out[7]: 
{'5': {'d': 33, 'e': 'another string', 'other_dict': {'foo': 'bar'}},
 'a': '42',
 'b': 'my_string'}
</code></pre>

<p>You should also be aware that if you had certain other objects in the dict like <em>datetime</em> objects etc.. then literal_eval would fail so it really depends on what your dict can contain as to whether it will work or not.</p>

<p>If you need a recursive approach, all you need is to call reassign on the new dict.</p>

<pre><code>def reassign(d):
    for k, v in d.items():
        try:
            evald = literal_eval(v)
            if isinstance(evald, dict):
                d[k] = evald
                reassign(evald)
        except ValueError:
            pass
</code></pre>

<p>And again just pass the dict:</p>

<pre><code>In [10]: my_dict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': \"{'f' : 64}
    ...: \"}"}

In [11]: reassign(my_dict)

In [12]: my_dict
Out[12]: {'a': 42, 'b': 'my_string', 'c': {'d': 33, 'e': {'f': 64}}}
</code></pre>

<p>And if you want a new dict:</p>

<pre><code>from ast import literal_eval
from copy import deepcopy

def reassign(d):
    for k, v in d.items():
        try:
            evald = literal_eval(v)
            if isinstance(evald, dict):
                yield k, dict(reassign(evald))
        except ValueError:
            yield k, deepcopy(v)
</code></pre>

<p>Which will give you a new dict:</p>

<pre><code>In [17]: my_dict = {'a': [1, 2, [3]], 'b': "my_string", 'c': "{'d': 33, 'e': \"{
    ...: 'f' : 64}\"}"}

In [18]: new =  dict(reassign(my_dict))

In [19]: my_dict["a"][-1].append(4)

In [20]: new
Out[20]: {'a': [1, 2, [3]], 'b': 'my_string', 'c': {'d': 33, 'e': {'f': 64}}}

In [21]: my_dict
Out[21]: 
{'a': [1, 2, [3, 4]],
 'b': 'my_string',
 'c': '{\'d\': 33, \'e\': "{\'f\' : 64}"}'}
</code></pre>

<p>You need to make sure to <em>deepcopy</em> objects or you won't get a true independent copy of the dict when you have nested object like  the list of lists above.</p>

python string python-3.x dictionary recursion