from typing import Any, Optional import orjson from django.conf import settings from drf_orjson_renderer.parsers import ORJSONParser from rest_framework.exceptions import ParseError def _underscoreize_key(key: str, no_underscore_before_number: bool = False) -> str: if not isinstance(key, str) or not key: return key out = [] prev_lower = False for ch in key: if ch.isupper(): if out and ( prev_lower or (not no_underscore_before_number and out[-1].isdigit()) ): out.append("_") out.append(ch.lower()) prev_lower = False elif ch.isdigit(): if out and not no_underscore_before_number and not out[-1].isdigit(): out.append("_") out.append(ch) prev_lower = False else: out.append(ch) prev_lower = True return "".join(out) def underscoreize(obj: Any, no_underscore_before_number: bool = False) -> Any: if isinstance(obj, dict): return { ( _underscoreize_key(k, no_underscore_before_number) if isinstance(k, str) else k ): underscoreize(v, no_underscore_before_number) for k, v in obj.items() } if isinstance(obj, (list, tuple)): t = type(obj) return t(underscoreize(v, no_underscore_before_number) for v in obj) return obj class CamelCaseParser(ORJSONParser): def parse( self, stream, media_type: Optional[Any] = None, parser_context: Any = None ) -> Any: parser_context = parser_context or {} encoding: str = parser_context.get("encoding", settings.DEFAULT_CHARSET) try: raw = stream.read().decode(encoding) data = orjson.loads(raw) except ValueError as exc: raise ParseError(f"JSON parse error - {exc}") from exc no_us_before_number = ( getattr(settings, "REST_FRAMEWORK", {}) .get("JSON_UNDERSCOREIZE", {}) .get("no_underscore_before_number", False) ) return underscoreize(data, no_underscore_before_number=no_us_before_number) __all__ = ["CamelCaseParser", "underscoreize"]