TypeError: 'str' object is not a mapping (tested fix)
TypeError: 'str' object is not a mapping (or 'list', 'NoneType') means **value got something that isn't a dict. With strings it's usually JSON that was never parsed. We reproduced each variant in Python 3.13 and tested the fixes.
1. Unpacking with ** something that is not a dict
The exact wording is produced by {**value} inside a dict literal. The type name tells you what you actually had:
for value in ('{"a": 1}', [("a", 1)], None, 5):
try:
print({**value})
except TypeError as e:
print(type(e).__name__ + ":", e)
print(dict([("a", 1)]))
Output:
TypeError: 'str' object is not a mapping
TypeError: 'list' object is not a mapping
TypeError: 'NoneType' object is not a mapping
TypeError: 'int' object is not a mapping
{'a': 1}
A list of pairs isn't a mapping either. Convert it with dict(pairs) first.
2. In a function call the message is worded differently
def f(**kw):
return kw
for value in ('{"a": 1}', [("a", 1)], None):
try:
print(f(**value))
except TypeError as e:
print(type(e).__name__ + ":", e)
Output:
TypeError: __main__.f() argument after ** must be a mapping, not str
TypeError: __main__.f() argument after ** must be a mapping, not list
TypeError: __main__.f() argument after ** must be a mapping, not NoneType
Same cause, different text: argument after ** must be a mapping, not str, with the function's name in front. Search your code for both forms.
3. The usual str case: JSON text that was never parsed
import json
payload = '{"name": "ann", "age": 31}' # JSON text, not a dict
def greet(name, age):
return f"{name} is {age}"
try:
print(greet(**payload))
except TypeError as e:
print(type(e).__name__ + ":", e)
print(greet(**json.loads(payload)))
Output:
TypeError: __main__.greet() argument after ** must be a mapping, not str
ann is 31
A response body, environment variable or file read gives you a string that looks like a dict. Parse it with json.loads() (or response.json()) before using **.
4. Other dict operations on a string fail with other messages
template = "Hello {name}, you are {age}"
for data in ({"name": "ann", "age": 31}, '{"name": "ann"}'):
try:
print(template.format_map(data))
except (TypeError, KeyError, AttributeError) as e:
print(type(e).__name__ + ":", e)
Output:
Hello ann, you are 31
TypeError: string indices must be integers, not 'str'
for left in ({"a": 1}, '{"a": 1}'):
try:
print(left | {"b": 2})
except TypeError as e:
print(type(e).__name__ + ":", e)
d = {"a": 1}
try:
d.update("ab")
except (TypeError, ValueError) as e:
print(type(e).__name__ + ":", e)
print(d)
Output:
{'a': 1, 'b': 2}
TypeError: unsupported operand type(s) for |: 'str' and 'dict'
ValueError: dictionary update sequence element #0 has length 1; 2 is required
{'a': 1}
So an unparsed JSON string can surface as string indices must be integers, unsupported operand type(s) for | or a ValueError from dict.update, depending on what you did with it. The fix is the same: parse it first.
5. What counts as a mapping
from collections.abc import Mapping
class Config:
def __init__(self):
self.data = {"debug": True}
def keys(self):
return self.data.keys()
def __getitem__(self, k):
return self.data[k]
print({**Config()}, isinstance(Config(), Mapping))
Output:
{'debug': True} False
** only needs keys() and __getitem__. The object doesn't have to be a dict or a registered Mapping. Adding those two methods to your own class makes it unpackable. Related: string indices must be integers and got an unexpected keyword argument.
Frequently asked questions
How do I fix 'str' object is not a mapping?
The value is a string, usually JSON text. Parse it with json.loads() to get a dict, then unpack it with **.
What does argument after ** must be a mapping, not str mean?
Same error in a function call: f(**value) where value is a string. Parse it or pass a dict.
How do I fix 'list' object is not a mapping?
If it's a list of (key, value) pairs, convert it with dict(pairs) before using **.
Written with AI assistance. Measured on 2026-10-22 on Debian with Python 3.13.5 (the ** messages also in the official python:3.11-alpine image, identical): {**x} and f(**x) with str/list/None/int, unparsed JSON, format_map, dict |, dict.update, and a duck-typed class with keys() and __getitem__. Every block was run verbatim and its output pasted unedited. PlainHeaders is our extension; Python is free.