Browse Source

Refs #35718, Refs #32179 -- Moved JSONObject to django.db.models.functions.json.

Sage Abdullah 3 months ago
parent
commit
d7d711c68c

+ 4 - 2
django/db/models/functions/__init__.py

@@ -1,4 +1,4 @@
-from .comparison import Cast, Coalesce, Collate, Greatest, JSONObject, Least, NullIf
+from .comparison import Cast, Coalesce, Collate, Greatest, Least, NullIf
 from .datetime import (
     Extract,
     ExtractDay,
@@ -25,6 +25,7 @@ from .datetime import (
     TruncWeek,
     TruncYear,
 )
+from .json import JSONObject
 from .math import (
     Abs,
     ACos,
@@ -97,7 +98,6 @@ __all__ = [
     "Coalesce",
     "Collate",
     "Greatest",
-    "JSONObject",
     "Least",
     "NullIf",
     # datetime
@@ -125,6 +125,8 @@ __all__ = [
     "TruncTime",
     "TruncWeek",
     "TruncYear",
+    # json
+    "JSONObject",
     # math
     "Abs",
     "ACos",

+ 0 - 62
django/db/models/functions/comparison.py

@@ -1,9 +1,6 @@
 """Database functions that do comparisons or type conversions."""
 
-from django.db import NotSupportedError
 from django.db.models.expressions import Func, Value
-from django.db.models.fields import TextField
-from django.db.models.fields.json import JSONField
 from django.utils.regex_helper import _lazy_re_compile
 
 
@@ -143,65 +140,6 @@ class Greatest(Func):
         return super().as_sqlite(compiler, connection, function="MAX", **extra_context)
 
 
-class JSONObject(Func):
-    function = "JSON_OBJECT"
-    output_field = JSONField()
-
-    def __init__(self, **fields):
-        expressions = []
-        for key, value in fields.items():
-            expressions.extend((Value(key), value))
-        super().__init__(*expressions)
-
-    def as_sql(self, compiler, connection, **extra_context):
-        if not connection.features.has_json_object_function:
-            raise NotSupportedError(
-                "JSONObject() is not supported on this database backend."
-            )
-        return super().as_sql(compiler, connection, **extra_context)
-
-    def join(self, args):
-        pairs = zip(args[::2], args[1::2], strict=True)
-        # Wrap 'key' in parentheses in case of postgres cast :: syntax.
-        return ", ".join([f"({key}) VALUE {value}" for key, value in pairs])
-
-    def as_native(self, compiler, connection, *, returning, **extra_context):
-        return self.as_sql(
-            compiler,
-            connection,
-            arg_joiner=self,
-            template=f"%(function)s(%(expressions)s RETURNING {returning})",
-            **extra_context,
-        )
-
-    def as_postgresql(self, compiler, connection, **extra_context):
-        # Casting keys to text is only required when using JSONB_BUILD_OBJECT
-        # or when using JSON_OBJECT on PostgreSQL 16+ with server-side bindings.
-        # This is done in all cases for consistency.
-        copy = self.copy()
-        copy.set_source_expressions(
-            [
-                Cast(expression, TextField()) if index % 2 == 0 else expression
-                for index, expression in enumerate(copy.get_source_expressions())
-            ]
-        )
-
-        if connection.features.is_postgresql_16:
-            return copy.as_native(
-                compiler, connection, returning="JSONB", **extra_context
-            )
-
-        return super(JSONObject, copy).as_sql(
-            compiler,
-            connection,
-            function="JSONB_BUILD_OBJECT",
-            **extra_context,
-        )
-
-    def as_oracle(self, compiler, connection, **extra_context):
-        return self.as_native(compiler, connection, returning="CLOB", **extra_context)
-
-
 class Least(Func):
     """
     Return the minimum expression.

+ 64 - 0
django/db/models/functions/json.py

@@ -0,0 +1,64 @@
+from django.db import NotSupportedError
+from django.db.models.expressions import Func, Value
+from django.db.models.fields import TextField
+from django.db.models.fields.json import JSONField
+from django.db.models.functions import Cast
+
+
+class JSONObject(Func):
+    function = "JSON_OBJECT"
+    output_field = JSONField()
+
+    def __init__(self, **fields):
+        expressions = []
+        for key, value in fields.items():
+            expressions.extend((Value(key), value))
+        super().__init__(*expressions)
+
+    def as_sql(self, compiler, connection, **extra_context):
+        if not connection.features.has_json_object_function:
+            raise NotSupportedError(
+                "JSONObject() is not supported on this database backend."
+            )
+        return super().as_sql(compiler, connection, **extra_context)
+
+    def join(self, args):
+        pairs = zip(args[::2], args[1::2], strict=True)
+        # Wrap 'key' in parentheses in case of postgres cast :: syntax.
+        return ", ".join([f"({key}) VALUE {value}" for key, value in pairs])
+
+    def as_native(self, compiler, connection, *, returning, **extra_context):
+        return self.as_sql(
+            compiler,
+            connection,
+            arg_joiner=self,
+            template=f"%(function)s(%(expressions)s RETURNING {returning})",
+            **extra_context,
+        )
+
+    def as_postgresql(self, compiler, connection, **extra_context):
+        # Casting keys to text is only required when using JSONB_BUILD_OBJECT
+        # or when using JSON_OBJECT on PostgreSQL 16+ with server-side bindings.
+        # This is done in all cases for consistency.
+        copy = self.copy()
+        copy.set_source_expressions(
+            [
+                Cast(expression, TextField()) if index % 2 == 0 else expression
+                for index, expression in enumerate(copy.get_source_expressions())
+            ]
+        )
+
+        if connection.features.is_postgresql_16:
+            return copy.as_native(
+                compiler, connection, returning="JSONB", **extra_context
+            )
+
+        return super(JSONObject, copy).as_sql(
+            compiler,
+            connection,
+            function="JSONB_BUILD_OBJECT",
+            **extra_context,
+        )
+
+    def as_oracle(self, compiler, connection, **extra_context):
+        return self.as_native(compiler, connection, returning="CLOB", **extra_context)

+ 30 - 25
docs/ref/models/database-functions.txt

@@ -163,31 +163,6 @@ and ``comment.modified``.
     The PostgreSQL behavior can be emulated using ``Coalesce`` if you know
     a sensible minimum value to provide as a default.
 
-``JSONObject``
---------------
-
-.. class:: JSONObject(**fields)
-
-Takes a list of key-value pairs and returns a JSON object containing those
-pairs.
-
-Usage example:
-
-.. code-block:: pycon
-
-    >>> from django.db.models import F
-    >>> from django.db.models.functions import JSONObject, Lower
-    >>> Author.objects.create(name="Margaret Smith", alias="msmith", age=25)
-    >>> author = Author.objects.annotate(
-    ...     json_object=JSONObject(
-    ...         name=Lower("name"),
-    ...         alias="alias",
-    ...         age=F("age") * 2,
-    ...     )
-    ... ).get()
-    >>> author.json_object
-    {'name': 'margaret smith', 'alias': 'msmith', 'age': 50}
-
 ``Least``
 ---------
 
@@ -861,6 +836,36 @@ that deal with time-parts can be used with ``TimeField``:
     2014-06-16 00:00:00+10:00 2
     2016-01-01 04:00:00+11:00 1
 
+.. _json-functions:
+
+JSON Functions
+==============
+
+``JSONObject``
+--------------
+
+.. class:: JSONObject(**fields)
+
+Takes a list of key-value pairs and returns a JSON object containing those
+pairs.
+
+Usage example:
+
+.. code-block:: pycon
+
+    >>> from django.db.models import F
+    >>> from django.db.models.functions import JSONObject, Lower
+    >>> Author.objects.create(name="Margaret Smith", alias="msmith", age=25)
+    >>> author = Author.objects.annotate(
+    ...     json_object=JSONObject(
+    ...         name=Lower("name"),
+    ...         alias="alias",
+    ...         age=F("age") * 2,
+    ...     )
+    ... ).get()
+    >>> author.json_object
+    {'name': 'margaret smith', 'alias': 'msmith', 'age': 50}
+
 .. _math-functions:
 
 Math Functions

+ 0 - 0
tests/db_functions/json/__init__.py


+ 0 - 0
tests/db_functions/comparison/test_json_object.py → tests/db_functions/json/test_json_object.py