Skip to content

How to Convert String to Nested Dictionary in Python

Learn how to safely parse Python literal strings and JSON into nested dictionaries using ast.literal_eval() and json.loads(), including format detection and error handling.

Parsing nested dictionary strings requires identifying the format first: Python literal syntax uses single quotes and Python keywords (True, None), while JSON uses double quotes and its own keywords (true, null). Using the wrong parser causes errors.

Parse Python Literal Strings with ast.literal_eval()

When the string contains single quotes or Python-specific values like True, False, or None, use the ast.literal_eval() function.

import ast

python_string = "{'user': {'id': 1, 'active': True, 'data': None}}"

data = ast.literal_eval(python_string)

print(data)

print(data['user']['active'])

print(type(data))

Handle Deeply Nested Structures

import ast

complex_string = """
{
    'level1': {
        'level2': {
            'level3': {
                'value': [1, 2, 3],
                'flag': True
            }
        }
    }
}
"""

data = ast.literal_eval(complex_string)

print(data['level1']['level2']['level3']['value'])

Parse JSON Strings with json.loads()

When the string uses double quotes and JSON keywords (true, false, null), use the json module.

import json

json_string = '{"user": {"id": 1, "active": true, "data": null}}'

data = json.loads(json_string)

print(data)

print(data['user']['active'])

Parse JSON API Responses

import json

api_response = '''
{
    "status": "success",
    "data": {
        "users": [
            {"id": 1, "profile": {"name": "Alice", "verified": true}},
            {"id": 2, "profile": {"name": "Bob", "verified": false}}
        ]
    }
}
'''

data = json.loads(api_response)

for user in data['data']['users']:
    name = user['profile']['name']
    verified = user['profile']['verified']
    print(f"{name}: {'✓' if verified else '✗'}")

Output:

Alice: ✓
Bob: ✗

Identify the Correct Format

Examine the string to determine which parser to use.

Indicator Format Parser
Single quotes ('key') Python ast.literal_eval()
Double quotes ("key") JSON json.loads()
True / False Python ast.literal_eval()
true / false JSON json.loads()
None Python ast.literal_eval()
null JSON json.loads()

Auto-Detect and Parse

import ast
import json

def parse_nested_dict(s: str) -> dict:
    """Parse string to dict, auto-detecting format."""
    s = s.strip()
    
    try:
        return json.loads(s)
    except json.JSONDecodeError:
        pass
    
    try:
        result = ast.literal_eval(s)
        if isinstance(result, dict):
            return result
        raise ValueError("String does not represent a dictionary")
    except (ValueError, SyntaxError) as e:
        raise ValueError(f"Cannot parse string as dict: {e}")

json_str = '{"nested": {"key": true}}'
print(parse_nested_dict(json_str))

python_str = "{'nested': {'key': True}}"
print(parse_nested_dict(python_str))

Avoid the Quote Replacement Trap

Never attempt to convert between formats by replacing quotes. This approach fails with embedded quotes in string values.

import json

python_string = "{'message': \"It's working\"}"

broken = python_string.replace("'", '"')
print(broken)

try:
    json.loads(broken)
except json.JSONDecodeError as e:
    print(f"Parse failed: {e}")

import ast
data = ast.literal_eval(python_string)
print(data)

Handle Parsing Errors

Implement robust error handling for production code.

import ast
import json

def safe_parse_dict(s: str, default: dict = None) -> dict:
    """Safely parse nested dict string with fallback."""
    if default is None:
        default = {}
    
    if not s or not s.strip():
        return default
    
    try:
        result = json.loads(s)
        return result if isinstance(result, dict) else default
    except (json.JSONDecodeError, TypeError):
        pass
    
    try:
        result = ast.literal_eval(s)
        return result if isinstance(result, dict) else default
    except (ValueError, SyntaxError):
        pass
    
    return default

print(safe_parse_dict('{"a": {"b": 1}}'))

print(safe_parse_dict('not a dict'))

print(safe_parse_dict('invalid', {'fallback': True}))

Access Nested Values Safely

Once parsed, access deeply nested values with error handling.

def get_nested(data: dict, *keys, default=None):
    """Safely get nested dictionary value."""
    for key in keys:
        try:
            data = data[key]
        except (KeyError, TypeError, IndexError):
            return default
    return data

config = {
    'database': {
        'primary': {
            'host': 'localhost',
            'port': 5432
        }
    }
}

print(get_nested(config, 'database', 'primary', 'host'))

print(get_nested(config, 'database', 'backup', 'host', default='N/A'))

Quick Reference

String Format Parser Example
Python literals ast.literal_eval(s) "{'key': True}"
JSON json.loads(s) '{"key": true}'
Unknown format Try both with error handling See parse_nested_dict()

Conclusion

Use ast.literal_eval() for Python-formatted strings from logs, configuration files, or debugging output. Use json.loads() for JSON from web APIs or data interchange. Never attempt manual quote replacement: it creates subtle bugs with special characters. When the format is unknown, implement a parser that tries both methods with proper error handling.

Navigation