Re: [Django] #35381: Provide `JSONNull` expression to represent JSON `null` value

34 views
Skip to first unread message

Django

unread,
Jul 7, 2025, 5:16:05 AM7/7/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Clifford Gama):

* owner: (none) => Clifford Gama
* status: new => assigned

--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:15>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jul 16, 2025, 10:16:55 AM7/16/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls):

#36508 was a dupe. We'll want to highlight this deprecation prominently in
the release notes, since unlike top-level JSON nulls, JSON nulls in
key/value pairs can be expected to appear more frequently.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:16>

Django

unread,
Jul 18, 2025, 10:17:09 AM7/18/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Simon Charette):

Another edge case of the dual interpretation of `None`
[https://docs.djangoproject.com/en/5.2/topics/db/queries/#storing-and-
querying-for-none when storing and querying JSONField] (SQL `NULL` on
persisting and `filter(json_field=None)` on filtering) that came up at
work today is how it breaks `get_or_create` expectations that the filter
and creation value will match.

It means that doing

{{{#!python
Foo.objects.get_or_create(bar=123, json_field=None)
}}}

will perform

{{{#!python
SELECT * FROM foo WHERE bar = 123 AND json_field = 'null'::jsonb
INSERT INTO foo (bar, json_field) VALUES (123, NULL)
}}}

using `JSONNull()` allows for JSON `null` to be used in both cases
(querying and storing) but there are no ways to specify that SQL `NULL`
must be used in both cases.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:17>

Django

unread,
Aug 25, 2025, 6:25:55 AM8/25/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Clifford Gama):

I’d like clarification on whether we also want to deprecate
`filter(data__key=None)`.

As I understand it, the ambiguity motivating deprecation at the top level
does not exist inside a JSON document. A key’s value can be JSON `null`,
but it cannot be SQL `NULL`. For that reason, I think
filter(data__key=None) should continue to match JSON `null`.

We could still allow `.filter(data__key=JSONNull())` for users who want
explicitness, without deprecating `None` usage in this case, especially as
Jacob pointed out, this case is more common. Additionally, on the creation
side, supporting nested JSON null like {"key": JSONNull()} in
inserts/updates would require a custom encoder, which isn’t the case for
`JSONNull()` at the top level. Since users can already override
encoders/decoders, we can’t make that work universally. We could bypass
this problem by introducing and promoting (in the docs) the use of
`__is_jsonnull` lookup for querying and JSONNull() for creating top-level
JSON `null`.

If we don’t deprecate using `None` in `KeyTransform` lookups to mean JSON
null, then ticket #36508 would have to be reopened, as it is surprising
that `(jsonfield__key__iexact=None)` is translated to an `__isnull` lookup
when `jsonfield__key=None` means "does key have a value of JSON `null`."
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:18>

Django

unread,
Aug 25, 2025, 4:33:46 PM8/25/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls):

A wrinkle is that `__exact=None` is pretty
[https://docs.djangoproject.com/en/5.2/ref/models/querysets/#exact clearly
documented] to be synonymous with `__isnull=True`, so by not deprecating
we'd be choosing to memorialize something I found as a user to be a
"gotcha".

> Additionally, on the creation side, supporting nested JSON null like
{"key": JSONNull()} in inserts/updates would require a custom encoder,
which isn’t the case for JSONNull() at the top level.

Would you mind fleshing this out slightly, to make sure I'm catching your
drift?
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:19>

Django

unread,
Aug 26, 2025, 3:57:18 AM8/26/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Clifford Gama):

> A wrinkle is that `__exact=None` is pretty ​clearly documented to be
synonymous with `__isnull=True`, so by not deprecating we'd be choosing to
memorialize something I found as a user to be a "gotcha".

Makes sense.

> Would you mind fleshing this out slightly, to make sure I'm catching
your drift?

I meant that doing `Dog(data={"name": JSONNull()}).save()}` would probably
require us to build a custom encoder since `json.dumps()` will not
recognise the `JSONNull()` expression.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:20>

Django

unread,
Aug 26, 2025, 7:51:36 AM8/26/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls):

> I meant that doing Dog(data={"name": JSONNull()}).save()} would probably
require us to build a custom encoder since json.dumps() will not recognise
the JSONNull() expression.

Thanks. Simon discussed this concern for top-level JSON `null` in
comment:5:

> It leaves the problem of having JSON null not surviving a round trip to
the database as both SQL NULL and json.loads("null") are turned into
Python None but that's a different issue that can be addressed with a
specialized decoder if users require it.

... which I think is an acceptable trade-off for top-level JSON null.

For nulls in key/value pairs, as in your example, I think it's still an
acceptable trade-off, as long as we are not *removing* the ability of
users to store python `None` in key/value pairs. The plan I see from the
above comments is just to deprecate & change the behavior of filtering on
`None` in key/value pairs.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:21>

Django

unread,
Aug 26, 2025, 8:02:47 AM8/26/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Simon Charette):

I share your position Jacob.

To me the ambiguity of what to do with `None` only exists for top level
values of `JSONField(null=True)`; there's no ambiguity for key values as
it always mean JSON `null`.

To make it clear I also think that `filter(data__key=None)` should
continue to match JSON `null` and I'm not opposed to supporting
`filter(data__key=JSONNull())` while I don't see much benefits to it.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:22>

Django

unread,
Aug 26, 2025, 8:29:30 AM8/26/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls):

Oh this clarifies things for me. I'll edit my recent comments to avoid
suggesting there was consensus to change the behavior of filtering on
`None` in key values, since it sounds like that was only proposed in
comment:4, and later as Clifford points out, in the triage decision on
#36508, but now we have multiple people with misgivings about doing that.

Having just left a project that managed most of its data in JSONFields, I
can offer that auditing the behavior of key=var lookups everywhere to
figure out what's intended when `var` is possibly `None` would be a
daunting expense.

Re: the "gotcha" of `exact=None` and `isnull=True` not being invariant, we
can just doc it clearly.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:23>

Django

unread,
Aug 26, 2025, 8:34:40 AM8/26/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Sage Abdullah):

Thanks all, I was just about to post what Simon said.

Replying to [comment:18 Clifford Gama]:
> If we don’t deprecate using `None` in `KeyTransform` lookups to mean
JSON null, then ticket #36508 would have to be reopened, as it is
surprising that `(jsonfield__key__iexact=None)` is translated to an
`__isnull` lookup when `jsonfield__key=None` means "does key have a value
of JSON `null`."

I agree, that ticket is a separate issue and I think it should be
reopened. I'll leave a comment there for more details.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:24>

Django

unread,
Aug 26, 2025, 9:02:43 AM8/26/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Clifford Gama):

Thank you all for clarifying things
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:25>

Django

unread,
Aug 29, 2025, 10:19:22 AM8/29/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Clifford Gama):

* has_patch: 0 => 1

--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:26>

Django

unread,
Sep 26, 2025, 4:04:53 PM9/26/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* needs_better_patch: 0 => 1

Comment:

A couple Oracle failures to look at.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:27>

Django

unread,
Oct 23, 2025, 10:31:06 AM10/23/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Clifford Gama):

* needs_better_patch: 1 => 0

--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:28>

Django

unread,
Oct 24, 2025, 3:46:08 PM10/24/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* needs_better_patch: 0 => 1

Comment:

Looking great, just a couple edits left.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:29>

Django

unread,
Oct 27, 2025, 12:35:05 PM10/27/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* needs_better_patch: 1 => 0
* stage: Accepted => Ready for checkin

Comment:

All but RFC, would just like to rebase after merging
[https://github.com/django/django/pull/20005 this cleanup] first.
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:30>

Django

unread,
Oct 27, 2025, 12:48:50 PM10/27/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Jacob Walls:

Old description:

> From Simon Charette in comment:3, in reference to the issues between
> SQL's `NULLL` and JSON's `null`:
>
> In order to finally solve this null ambiguity problem we should:
>
> 1. Introduce a `JSONNull` expression to disambiguate between the two. It
> would also allow the creation of model instances with a JSON `null` which
> is convoluted today as it requires `models.Value(None,
> models.JSONField())` to be used.
> 2. Deprecate `filter(jsonfield=None)` meaning JSON `null` by requiring
> `JSONNull` to be used instead. Should we only do this at the top level to
> still allow `jsonfield__key=None` to filter against null keys? An
> alternative would be to introduce a `__jsonnull` lookup.
>
> The good news is that Django makes it very hard to store JSON `null` in
> the first place since #34754 so this kind of constraints should be rarely
> needed.
>
> == Old description ==
>
> Regression is still present in 5.0 and main branch
>
> given a model defined as follow
>
> {{{
> from django.db import models
>

> class JSONFieldModel(models.Model):
> data = models.JSONField(null=True, blank=True, default=None)
>
> class Meta:
> required_db_features = {"supports_json_field"}
> constraints = [
> models.CheckConstraint(
> check=~models.Q(data=models.Value(None,
> models.JSONField())),
> name="json_data_cant_be_json_null",
> ),
> ]
>
> }}}
>
> the following test works in django 4.1.13, fails in later version
>
> {{{
> from django.test import TestCase, skipUnlessDBFeature
>
> from .models import JSONFieldModel
>

> class CheckConstraintTests(TestCase):
> @skipUnlessDBFeature("supports_json_field")
> def test_full_clean_on_null_value(self):
> instance = JSONFieldModel.objects.create(data=None) # data = SQL
> Null value
> instance.full_clean()
>
> }}}
>
> {{{
> tests/runtests.py json_null_tests
> ======================================================================
> ERROR: test_full_clean_on_null_value
> (json_null_tests.tests.CheckConstraintTests.test_full_clean_on_null_value)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
> File
> "/Users/olivier/dev/django.dev/olibook/django/django/test/testcases.py",
> line 1428, in skip_wrapper
> return test_func(*args, **kwargs)
> ^^^^^^^^^^^^^^^^^^^^^^^^^^
> File
> "/Users/olivier/dev/django.dev/olibook/django/tests/json_null_tests/tests.py",
> line 13, in test_full_clean_on_null_value
> instance.full_clean()
> File
> "/Users/olivier/dev/django.dev/olibook/django/django/db/models/base.py",
> line 1504, in full_clean
> raise ValidationError(errors)
> django.core.exceptions.ValidationError: {'__all__': ['Constraint
> “json_data_cant_be_json_null” is violated.']}
>
> ----------------------------------------------------------------------
> }}}
>
> Attached a patch that will create run json_null_tests folder in django's
> `tests` directories. Test can be run with
>
> {{{
> tests/runtests.py json_null_tests
> }}}
>
> NOTE : in django 5.1 the `CheckConstraint.check` parameter as been
> renamed to `CheckConstraint.condition`.
>
> I ran a git bisect on this test case:
>
> 8fcb9f1f106cf60d953d88aeaa412cc625c60029 is bad
> e14d08cd894e9d91cb5d9f44ba7532c1a223f458 is good
>
> {{{
> git bisect run tests/runtests.py json_null_tests
> }}}
>
> 5c23d9f0c32f166c81ecb6f3f01d5077a6084318 is identified as first bad
> commit
>
> the issue appears with both sqlite and postgres backends
>
> A few words on what we I'm trying to achieve (some context on the
> regression):
>
> The `json_data_cant_be_json_null` aims to prevent having `JsonModel.data
> = None` (SQL Null) and `JSONModel.data = Value(None, JSONField())` (JSON
> null) values in the database. These leads to unwanted behaviors when
> filtering on the null value, and I settled on representing the absence of
> value with SQL Null and not JSON null.
>
> The app was running in django 4.1 and I'm upgrading it to later django
> version. That's how the issue appeared.

New description:

From Simon Charette in comment:3, in reference to the issues between SQL's
`NULL` and JSON's `null`:

In order to finally solve this null ambiguity problem we should:

1. Introduce a `JSONNull` expression to disambiguate between the two. It
would also allow the creation of model instances with a JSON `null` which
is convoluted today as it requires `models.Value(None,
models.JSONField())` to be used.
2. Deprecate `filter(jsonfield=None)` meaning JSON `null` by requiring
`JSONNull` to be used instead. Should we only do this at the top level to
still allow `jsonfield__key=None` to filter against null keys? An
alternative would be to introduce a `__jsonnull` lookup.

The good news is that Django makes it very hard to store JSON `null` in
the first place since #34754 so this kind of constraints should be rarely
needed.

== Old description ==

Regression is still present in 5.0 and main branch

given a model defined as follow

{{{
from django.db import models


class JSONFieldModel(models.Model):
data = models.JSONField(null=True, blank=True, default=None)

class Meta:
required_db_features = {"supports_json_field"}
constraints = [
models.CheckConstraint(
check=~models.Q(data=models.Value(None,
models.JSONField())),
name="json_data_cant_be_json_null",
),
]

}}}

the following test works in django 4.1.13, fails in later version

{{{
from django.test import TestCase, skipUnlessDBFeature

from .models import JSONFieldModel


class CheckConstraintTests(TestCase):
@skipUnlessDBFeature("supports_json_field")
def test_full_clean_on_null_value(self):
instance = JSONFieldModel.objects.create(data=None) # data = SQL
Null value
instance.full_clean()

}}}

{{{
tests/runtests.py json_null_tests
======================================================================
ERROR: test_full_clean_on_null_value
(json_null_tests.tests.CheckConstraintTests.test_full_clean_on_null_value)
----------------------------------------------------------------------
Traceback (most recent call last):
File
"/Users/olivier/dev/django.dev/olibook/django/django/test/testcases.py",
line 1428, in skip_wrapper
return test_func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File
"/Users/olivier/dev/django.dev/olibook/django/tests/json_null_tests/tests.py",
line 13, in test_full_clean_on_null_value
instance.full_clean()
File
"/Users/olivier/dev/django.dev/olibook/django/django/db/models/base.py",
line 1504, in full_clean
raise ValidationError(errors)
django.core.exceptions.ValidationError: {'__all__': ['Constraint
“json_data_cant_be_json_null” is violated.']}

----------------------------------------------------------------------
}}}

Attached a patch that will create run json_null_tests folder in django's
`tests` directories. Test can be run with

{{{
tests/runtests.py json_null_tests
}}}

NOTE : in django 5.1 the `CheckConstraint.check` parameter as been renamed
to `CheckConstraint.condition`.

I ran a git bisect on this test case:

8fcb9f1f106cf60d953d88aeaa412cc625c60029 is bad
e14d08cd894e9d91cb5d9f44ba7532c1a223f458 is good

{{{
git bisect run tests/runtests.py json_null_tests
}}}

5c23d9f0c32f166c81ecb6f3f01d5077a6084318 is identified as first bad commit

the issue appears with both sqlite and postgres backends

A few words on what we I'm trying to achieve (some context on the
regression):

The `json_data_cant_be_json_null` aims to prevent having `JsonModel.data =
None` (SQL Null) and `JSONModel.data = Value(None, JSONField())` (JSON
null) values in the database. These leads to unwanted behaviors when
filtering on the null value, and I settled on representing the absence of
value with SQL Null and not JSON null.

The app was running in django 4.1 and I'm upgrading it to later django
version. That's how the issue appeared.

--
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:31>

Django

unread,
Oct 29, 2025, 3:01:04 PM10/29/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls <jacobtylerwalls@…>):

* resolution: => fixed
* status: assigned => closed

Comment:

In [changeset:"adc25a9a6696f7e2ab6181aabce3a23e027d6703" adc25a9]:
{{{#!CommitTicketReference repository=""
revision="adc25a9a6696f7e2ab6181aabce3a23e027d6703"
Fixed #35381 -- Added JSONNull() expression.

Thanks Jacob Walls for the review.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:32>

Django

unread,
Oct 29, 2025, 3:01:04 PM10/29/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls <jacobtylerwalls@…>):

In [changeset:"be7f68422d4c6ae568a17f1fa91aac67d284df82" be7f6842]:
{{{#!CommitTicketReference repository=""
revision="be7f68422d4c6ae568a17f1fa91aac67d284df82"
Refs #35381 -- Delegated ArrayField element prepping to
base_field.get_db_prep_save.

Previously, ArrayField always used base_field.get_db_prep_value when
saving,
which could differ from how base_field prepares data for save. This change
overrides ArrayField.get_db_prep_save to delegate to the base_field's
get_db_prep_save, ensuring elements like None in JSONField arrays are
saved
correctly as SQL NULL instead of JSON null.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:33>

Django

unread,
Oct 29, 2025, 3:01:04 PM10/29/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls <jacobtylerwalls@…>):

In [changeset:"348ca845385beaddc7c862ff8ec369f041a5088d" 348ca845]:
{{{#!CommitTicketReference repository=""
revision="348ca845385beaddc7c862ff8ec369f041a5088d"
Refs #35381 -- Deprecated using None in JSONExact rhs to mean JSON null.

Key and index lookups are exempt from the deprecation.

Co-authored-by: Jacob Walls <jacobty...@gmail.com>
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:34>

Django

unread,
Oct 29, 2025, 3:01:04 PM10/29/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls <jacobtylerwalls@…>):

In [changeset:"7fc9db1c6a3a4865d85338f26812ce80f076ebec" 7fc9db1]:
{{{#!CommitTicketReference repository=""
revision="7fc9db1c6a3a4865d85338f26812ce80f076ebec"
Refs #35381 -- Clarified key and index lookup handling of None in exact
lookup docs.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:35>

Django

unread,
Nov 12, 2025, 5:15:07 PM11/12/25
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls <jacobtylerwalls@…>):

In [changeset:"66b5a6de78ac3bcdf586844eac61663fece10ab5" 66b5a6d]:
{{{#!CommitTicketReference repository=""
revision="66b5a6de78ac3bcdf586844eac61663fece10ab5"
Refs #35381 -- Made JSONNull deconstruct using convenient import path.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:36>

Django

unread,
Jan 9, 2026, 3:49:43 PMJan 9
to django-...@googlegroups.com
#35381: Provide `JSONNull` expression to represent JSON `null` value
-------------------------------------+-------------------------------------
Reporter: Olivier Tabone | Owner: Clifford
| Gama
Type: New feature | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls <jacobtylerwalls@…>):

In [changeset:"1d15c732bb9dde0fca8bd662a475bb51d0ab2756" 1d15c73]:
{{{#!CommitTicketReference repository=""
revision="1d15c732bb9dde0fca8bd662a475bb51d0ab2756"
Refs #35381 -- Added missing deprecation note for using None as RHS of
JSONExact.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/35381#comment:37>
Reply all
Reply to author
Forward
0 new messages