## The Frustrating Problem of Using Django's `gettext_lazy` as a JSON Key {#sec-66ae94ff1145} > One-line summary: A **JSON Key must be a 'real' string**, but Django's `gettext_lazy` actually returns a `__proxy__` object, not a string. ## 1. Why Did Perfectly Working Code Suddenly Break? {#sec-4a4b34e6a73c} In [[Django]] development, using `gettext_lazy` for internationalization (i18n) is a common practice. However, while using it normally, you might suddenly encounter a serialization error during a JSON response (e.g., using `JsonResponse`). Typically, the error logs will show cryptic messages about `__proxy__` objects, which can be quite perplexing. However, the root cause is often surprisingly simple. ![Infographic illustrating the gettext_lazy and JSON key problem](/media/whitedec/blog_img/gettext_lazy_json_error.webp) ## 2. The Culprit Was the "Key" {#sec-361c46af305d} To put it simply, a `gettext_lazy` object generally behaves well when used as a dictionary **Value**, but it causes problems the moment it's used as a **Key**. | Category | When used as Value | When used as Key | | :--- | :--- | :--- | | **Operation** | Normal (auto `str` conversion) | **Error occurs** | | **Reason** | Automatic type conversion supported during JSON serialization | JSON Keys must be 'real' strings | ## 3. Why This Difference? {#sec-80787a3802c2} What `gettext_lazy` returns is not a true String but an object called `__proxy__`. As its name implies, it's essentially a promise to "provide the translation later when needed." Python's `json.dumps()` or [[Django]]'s `JsonResponse` iterates through dictionary values, automatically fulfilling this 'promise' by converting `__proxy__` objects into strings (evaluation). However, the **Keys of a dictionary** are a different story. According to the [[JSON]] standard, keys must always be strings. When a `__proxy__` object encounters this requirement, it doesn't automatically convert, leading to an error. ```python # ✅ No problem: Values are automatically converted to str. json.dumps({'language': _('Korean')}) # ❌ Error: Keys are not automatically converted and are rejected. json.dumps({_('Korean'): 'language'}) ``` ## 4. How to Resolve This? {#sec-87b95d49caed} ### Method 1: Simply Use `gettext` (Cleanest Approach) {#sec-d736d0465485} If a lazy approach isn't strictly necessary, using `gettext`, which returns a string immediately upon call, is often the most straightforward solution. ```python from django.utils.translation import gettext as _ LANGUAGE_MAP = { "en": _("English"), "ko": _("Korean"), } ``` * **Note:** However, with this method, the translation is determined when the application loads. Caution is advised if a dynamic translation environment is crucial. ### Method 2: Force `str()` Conversion Before Serialization {#sec-fc5762a0f995} If you are already extensively using lazy objects, you can explicitly convert them to strings right before passing them to [[JSON]]. ```python # Wrap with str() before using as a Key to make it a 'real string'. lang_key = str(LANGUAGE_MAP.get(code)) ``` ### Method 3: Delegate Translation to the Client-Side {#sec-f61be7ca95b2} If you're using a separate frontend client instead of [[Django]] templates (which seems to be a growing trend these days), the [[Django]] (or [[DRF]]) server can simply send codes like `en` or `ko`. The actual text displayed on the screen would then be handled by the frontend's i18n library (e.g., Reac], Vue). This approach offers the advantage of lighter server-side logic. --- ## Conclusion {#sec-0d2251a657d4} If you've ever found yourself wondering, "It worked yesterday, why not today?" while using `json.dumps()` with `gettext_lazy()`, it's highly likely that yesterday you fortunately used it only for a Value, whereas today you used it as a Key. `gettext_lazy` is convenient, but it's crucial to always remember that it's not a 'real string.' This is especially important when dealing with [[JSON]]. I hope this helps anyone struggling with similar errors.