Skip to main content

How to Convert String to Nested Dictionary in Python

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)
# Output: {'user': {'id': 1, 'active': True, 'data': None}}

print(data['user']['active'])
# Output: True

print(type(data))
# Output: <class 'dict'>

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'])
# Output: [1, 2, 3]
tip

ast.literal_eval() safely evaluates strings containing Python literals (strings, numbers, tuples, lists, dicts, booleans, and None). It cannot execute arbitrary code, making it safe for untrusted input.

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)
# Output: {'user': {'id': 1, 'active': True, 'data': None}}

print(data['user']['active'])
# Output: True

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.

IndicatorFormatParser
Single quotes ('key')Pythonast.literal_eval()
Double quotes ("key")JSONjson.loads()
True / FalsePythonast.literal_eval()
true / falseJSONjson.loads()
NonePythonast.literal_eval()
nullJSONjson.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 JSON first (more common in APIs)
try:
return json.loads(s)
except json.JSONDecodeError:
pass

# Fall back to Python literal
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}")

# Works with JSON
json_str = '{"nested": {"key": true}}'
print(parse_nested_dict(json_str))
# Output: {'nested': {'key': True}}

# Works with Python literals
python_str = "{'nested': {'key': True}}"
print(parse_nested_dict(python_str))
# Output: {'nested': {'key': True}}

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

# ❌ BROKEN: Simple quote replacement
python_string = "{'message': \"It's working\"}"

# This creates invalid syntax
broken = python_string.replace("'", '"')
print(broken)
# Output: {"message": "It"s working"}

try:
json.loads(broken)
except json.JSONDecodeError as e:
print(f"Parse failed: {e}")
# Output: Parse failed: Expecting ',' delimiter: line 1 column 17 (char 16)

# ✅ CORRECT: Use ast for Python literals
import ast
data = ast.literal_eval(python_string)
print(data)
# Output: {'message': "It's working"}
warning

Quote replacement fails with apostrophes, escaped characters, and nested quotes. Always use the appropriate parser for the format.

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 JSON
try:
result = json.loads(s)
return result if isinstance(result, dict) else default
except (json.JSONDecodeError, TypeError):
pass

# Try Python literal
try:
result = ast.literal_eval(s)
return result if isinstance(result, dict) else default
except (ValueError, SyntaxError):
pass

return default

# Valid input
print(safe_parse_dict('{"a": {"b": 1}}'))
# Output: {'a': {'b': 1}}

# Invalid input returns default
print(safe_parse_dict('not a dict'))
# Output: {}

print(safe_parse_dict('invalid', {'fallback': True}))
# Output: {'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
}
}
}

# Safe access
print(get_nested(config, 'database', 'primary', 'host'))
# Output: localhost

# Missing key returns default
print(get_nested(config, 'database', 'backup', 'host', default='N/A'))
# Output: N/A

Quick Reference

String FormatParserExample
Python literalsast.literal_eval(s)"{'key': True}"
JSONjson.loads(s)'{"key": true}'
Unknown formatTry both with error handlingSee 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.