#36371 closed Bug (worksforme)
JSONField.from_db_value crashes when DB returns parsed JSON despite KeyTransform guard
Reported by: | Mason Pitts | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 5.2 |
Severity: | Normal | Keywords: | jsonfield, from_db_value, double-decoding, psycopg3, cx_oracle, python-oracledb |
Cc: | Mason Pitts | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
In Django 5.2, the default implementation of JSONField.from_db_value() only skips double-decoding when the ORM expression is a KeyTransform on a non-string. However, many modern database drivers (e.g. PostgreSQL psycopg3, Oracle DB_TYPE_JSON via cx_Oracle 8.1+/python-oracledb) will automatically deserialize JSON columns into native Python types (dict, list) before Django sees them. Since from_db_value() still unconditionally calls json.loads() in most cases, you get:
TypeError: the JSON object must be str, bytes or bytearray, not list
even though the value is already a valid Python object.
Here is the current code below as of 5/6/2025.
def from_db_value(self, value, expression, connection): if value is None: return value # Some backends (SQLite at least) extract non-string values in their # SQL datatypes. if isinstance(expression, KeyTransform) and not isinstance(value, str): return value try: return json.loads(value, cls=self.decoder) except json.JSONDecodeError: return value
Here is a potential solution that attempts to return value if it is a Python type.
def from_db_value(self, value, expression, connection): if value is None: return None # Decode binary data first. if isinstance(value, (bytes, bytearray)): value = value.decode() # If value isn’t a string at this point, the driver already gave us # a native Python type (dict, list, bool, int, float, ...). if not isinstance(value, str): return value try: return json.loads(value, cls=self.decoder) except json.JSONDecodeError: return value
Steps to reproduce:
- Define a model with a models.JSONField().
- Use a database and driver combination that natively decodes JSON columns—for example:
- PostgreSQL with psycopg3
- Oracle 21c+ JSON column type with cx_Oracle 8.1+ or python-oracledb in thin mode
- I encountered this problem using oracle_db in thin mode.
- Query the model in the Django admin or via MyModel.objects.all().
- Observe the traceback raising a TypeError when json.loads() is fed a list or dict.
Version information:
Django: 5.2
Python: 3.12.10
Affected drivers/backends:
PostgreSQL with psycopg3
Oracle 21c+ with cx_Oracle 8.1+ / python-oracledb in thin mode
Change History (2)
comment:1 by , 4 months ago
Easy pickings: | unset |
---|---|
Has patch: | unset |
Resolution: | → worksforme |
Status: | new → closed |
comment:2 by , 4 months ago
Likely related to tickets such as #31973, #32542.
You're likely using Postgres and have the column your JSONField
is pointed at defined with a json
type instead of jsonb
(not something created by Django itself as it never used the json
type).
The ORM explicitly request jsonb
columns not to be deserialized by the backend (psycopg2
and psycopg
) to support the JSONField.decoder
feature but since it doesn't use json
columns internally it leaves them as is.
Hello Mason Pitts, thank you for taking the time to create this report. I can't reproduce what you are describing: I'm using Django
main
, PostgreSQL 16.8 and psycopg version 3.2.3. Given this model:And this test case:
Running the tests shows:
Given this, I think this report seems better suited to be a support request. The best place to get answers to your issue is using any of the user support channels from this link.
Since the goal of this issue tracker is to track issues about Django itself, and your issue seems, at first, to be located in your custom code, I'll be closing this ticket following the ticket triaging process. If, after debugging, you find out that this is indeed a bug in Django, please re-open with the specific details and please be sure to include a small Django project to reproduce or a failing test case.