| 66 | |
| 67 | |
| 68 | class JSONObject(Func): |
| 69 | function = "JSON_OBJECT" |
| 70 | output_field = JSONField() |
| 71 | |
| 72 | def __init__(self, **fields): |
| 73 | expressions = [] |
| 74 | for key, value in fields.items(): |
| 75 | expressions.extend((Value(key), value)) |
| 76 | super().__init__(*expressions) |
| 77 | |
| 78 | def as_sql(self, compiler, connection, **extra_context): |
| 79 | if not connection.features.has_json_object_function: |
| 80 | raise NotSupportedError( |
| 81 | "JSONObject() is not supported on this database backend." |
| 82 | ) |
| 83 | return super().as_sql(compiler, connection, **extra_context) |
| 84 | |
| 85 | def join(self, args): |
| 86 | pairs = zip(args[::2], args[1::2], strict=True) |
| 87 | # Wrap 'key' in parentheses in case of postgres cast :: syntax. |
| 88 | return ", ".join([f"({key}) VALUE {value}" for key, value in pairs]) |
| 89 | |
| 90 | def as_native(self, compiler, connection, *, returning, **extra_context): |
| 91 | return self.as_sql( |
| 92 | compiler, |
| 93 | connection, |
| 94 | arg_joiner=self, |
| 95 | template=f"%(function)s(%(expressions)s RETURNING {returning})", |
| 96 | **extra_context, |
| 97 | ) |
| 98 | |
| 99 | def as_postgresql(self, compiler, connection, **extra_context): |
| 100 | # Casting keys to text is only required when using JSONB_BUILD_OBJECT |
| 101 | # or when using JSON_OBJECT on PostgreSQL 16+ with server-side |
| 102 | # bindings. This is done in all cases for consistency. |
| 103 | copy = self.copy() |
| 104 | copy.set_source_expressions( |
| 105 | [ |
| 106 | Cast(expression, TextField()) if index % 2 == 0 else expression |
| 107 | for index, expression in enumerate(copy.get_source_expressions()) |
| 108 | ] |
| 109 | ) |
| 110 | |
| 111 | if connection.features.is_postgresql_16: |
| 112 | return copy.as_native( |
| 113 | compiler, connection, returning="JSONB", **extra_context |
| 114 | ) |
| 115 | |
| 116 | return super(JSONObject, copy).as_sql( |
| 117 | compiler, |
| 118 | connection, |
| 119 | function="JSONB_BUILD_OBJECT", |
| 120 | **extra_context, |
| 121 | ) |
| 122 | |
| 123 | def as_oracle(self, compiler, connection, **extra_context): |
| 124 | return self.as_native(compiler, connection, returning="CLOB", **extra_context) |