Skip to content
CaseConvert

camelCase to snake_case in Python: Four Approaches

Convert camelCase strings to snake_case in Python using regex, str.join, or libraries. Includes code you can paste directly.

By Editorial Team Updated
  • python
  • snake case
  • camelcase
  • string manipulation
  • naming conventions
camelCase to snake_case in Python: Four Approaches

Python’s style guide (PEP 8) is clear: function names and variables should use snake_case. That means if you’re consuming a camelCase JSON API or migrating from a camelCase codebase, you’ll need to convert identifiers at some point.

Here are four approaches, from quick one-liner to production-ready.

The cleanest general-purpose solution inserts an underscore before each uppercase letter that follows a lowercase letter, then lowercases the whole string:

import re

def camel_to_snake(name: str) -> str:
    # Insert _ before sequences like "aB" → "a_B"
    s1 = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', name)
    # Also handle sequences like "ABCDef" → "AB_Cdef"
    s2 = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
    return s2.lower()

Examples:

camel_to_snake("firstName")       # → "first_name"
camel_to_snake("getUserById")     # → "get_user_by_id"
camel_to_snake("parseHTTPResponse")  # → "parse_http_response"
camel_to_snake("HTMLParser")      # → "html_parser"
camel_to_snake("myJSONKey")       # → "my_json_key"

The two-pass regex handles both standard camelCase and abbreviation runs (HTTP, JSON, HTML) correctly. Using only one re.sub pass breaks on consecutive capitals.

2. One-liner for simple cases

If your identifiers don’t contain consecutive uppercase letters (no abbreviations), a single regex is enough:

import re

snake = lambda s: re.sub(r'(?<!^)(?=[A-Z])', '_', s).lower()

snake("firstName")    # → "first_name"
snake("getUserById")  # → "get_user_by_id"
snake("parseJSON")    # → "parse_j_s_o_n"  ← breaks on abbreviations

Use this only when you’re confident your input won’t have runs of capitals.

3. Converting dictionary keys recursively

A common real-world need is normalizing JSON from a JavaScript API. The following function walks a nested dict/list structure and converts all keys:

import re

def _to_snake(name: str) -> str:
    s1 = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', name)
    s2 = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
    return s2.lower()

def camel_keys_to_snake(obj):
    if isinstance(obj, dict):
        return {_to_snake(k): camel_keys_to_snake(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [camel_keys_to_snake(i) for i in obj]
    return obj

Usage:

data = {
    "userId": 42,
    "firstName": "Alice",
    "address": {
        "streetName": "Main St",
        "zipCode": "12345"
    },
    "recentOrders": [
        {"orderId": 1, "totalAmount": 99.99}
    ]
}

print(camel_keys_to_snake(data))
# {
#   "user_id": 42,
#   "first_name": "Alice",
#   "address": {"street_name": "Main St", "zip_code": "12345"},
#   "recent_orders": [{"order_id": 1, "total_amount": 99.99}]
# }

4. Using a library

If you’re already using inflection or pyhumps, they handle this natively:

inflection:

import inflection

inflection.underscore("firstName")        # → "first_name"
inflection.underscore("parseHTTPResponse") # → "parse_http_response"

pyhumps:

import humps

humps.decamelize("firstName")   # → "first_name"

# Also converts dict keys automatically:
humps.decamelize({"userId": 1, "firstName": "Alice"})
# → {"user_id": 1, "first_name": "Alice"}

pyhumps is particularly useful for Pydantic models or FastAPI because it integrates with the alias generator pattern:

from pydantic import BaseModel
import humps

class UserModel(BaseModel):
    user_id: int
    first_name: str

    model_config = {
        "alias_generator": humps.camelize,
        "populate_by_name": True,
    }

# Now the model accepts camelCase JSON but uses snake_case attributes:
user = UserModel.model_validate({"userId": 1, "firstName": "Alice"})
print(user.first_name)  # → "Alice"

Choosing an approach

SituationRecommendation
One-off string conversionre.sub two-pass function
Normalizing JSON dict keysRecursive camel_keys_to_snake
Project already uses Pydantic/FastAPIpyhumps with alias generator
General text/identifier workinflection.underscore

Edge cases to watch

  • Numbers: camelCase2snake — numbers are treated as lowercase by most implementations. getUserV2get_user_v2 (usually correct).
  • Already snake_case input: The two-pass regex returns it unchanged.
  • Abbreviation handling: Test your regex on inputs like parseHTTPSURL, myAWSKey, JSONParser to verify you get sensible output.
  • Leading underscore: _privateName — the leading underscore is preserved by the regex above.

Quick online conversion

For bulk renaming — renaming a set of variables, converting an API response schema — the caseconvert.io tool converts entire text blocks between camelCase, snake_case, kebab-case, and PascalCase without writing any code.