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.