Repository navigation
fix(django-google-spanner): preserve JsonObject in JSONField adaptation and deserialization #18989
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -347,3 +347,37 @@ def test_datetime_cast_sql(self): | |
| "TIMESTAMP(FORMAT_TIMESTAMP('%Y-%m-%d %R:%E9S %Z', field, 'UTC'))", | ||
| ) | ||
| self.assertIsNone(params) | ||
|
|
||
| def test_adapt_json_value(self): | ||
| import uuid | ||
|
|
||
| from django.core.serializers.json import DjangoJSONEncoder | ||
| from django.db.models import JSONField | ||
| from google.cloud.spanner_v1 import JsonObject | ||
|
|
||
| self.assertIsNone(self.db_operations.adapt_json_value(None, None)) | ||
| obj = JsonObject({"a": 1}) | ||
| self.assertEqual(self.db_operations.adapt_json_value(obj, None), {"a": 1}) | ||
| adapted_dict = self.db_operations.adapt_json_value({"a": 1}, None) | ||
| self.assertIsInstance(adapted_dict, JsonObject) | ||
| self.assertEqual(adapted_dict, {"a": 1}) | ||
| adapted_list = self.db_operations.adapt_json_value([1, 2], None) | ||
| self.assertIsInstance(adapted_list, JsonObject) | ||
| self.assertEqual(adapted_list, [1, 2]) | ||
| adapted_str = self.db_operations.adapt_json_value("str", None) | ||
| self.assertIsInstance(adapted_str, JsonObject) | ||
| self.assertEqual(adapted_str, "str") | ||
|
|
||
| u = uuid.UUID("12345678-1234-5678-1234-567812345678") | ||
| adapted_uuid = self.db_operations.adapt_json_value({"u": u}, DjangoJSONEncoder) | ||
| self.assertIsInstance(adapted_uuid, JsonObject) | ||
| self.assertEqual(adapted_uuid, {"u": "12345678-1234-5678-1234-567812345678"}) | ||
|
|
||
| field = JSONField() | ||
| self.assertEqual(field.from_db_value(obj, None, None), {"a": 1}) | ||
| self.assertEqual(field.from_db_value(JsonObject([1, 2]), None, None), [1, 2]) | ||
| self.assertEqual(field.from_db_value(JsonObject("str"), None, None), "str") | ||
| self.assertIsNone(field.from_db_value(JsonObject(), None, None)) | ||
| self.assertEqual(field.from_db_value({"a": 1}, None, None), {"a": 1}) | ||
| self.assertEqual(field.from_db_value('{"a": 1}', None, None), {"a": 1}) | ||
| self.assertIsNone(field.from_db_value(None, None, None)) | ||
|
Comment on lines
+351
to
+383
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the unit tests to verify that primitive JSON values are correctly wrapped in def test_adapt_json_value(self):
import uuid
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import JSONField
from google.cloud.spanner_v1 import JsonObject
self.assertIsNone(self.db_operations.adapt_json_value(None, None))
obj = JsonObject({"a": 1})
self.assertIs(self.db_operations.adapt_json_value(obj, None), obj)
adapted_dict = self.db_operations.adapt_json_value({"a": 1}, None)
self.assertIsInstance(adapted_dict, JsonObject)
self.assertEqual(adapted_dict.value, {"a": 1})
adapted_list = self.db_operations.adapt_json_value([1, 2], None)
self.assertIsInstance(adapted_list, JsonObject)
self.assertEqual(adapted_list.value, [1, 2])
adapted_str = self.db_operations.adapt_json_value("str", None)
self.assertIsInstance(adapted_str, JsonObject)
self.assertEqual(adapted_str.value, "str")
# Test custom encoder serialization (e.g., UUID)
u = uuid.UUID("12345678-1234-5678-1234-567812345678")
adapted_uuid = self.db_operations.adapt_json_value({"u": u}, DjangoJSONEncoder)
self.assertIsInstance(adapted_uuid, JsonObject)
self.assertEqual(adapted_uuid.value, {"u": "12345678-1234-5678-1234-567812345678"})
field = JSONField()
self.assertIs(field.from_db_value(obj, None, None), obj)
self.assertEqual(field.from_db_value({"a": 1}, None, None), {"a": 1})
self.assertEqual(field.from_db_value('{"a": 1}', None, None), {"a": 1})
self.assertIsNone(field.from_db_value(None, None, None))
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
JsonObjectclass is used in theisinstancecheck but is not imported in this file, which will raise aNameErrorat runtime whenfrom_db_valueis called. ImportJsonObjectfromgoogle.cloud.spanner_v1inside the function to avoid circular dependencies and ensure runtime correctness.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsonObjectis already imported at the module level indjango_spanner/__init__.py(line 30). Also updatedjsonfield_from_db_valueto unwrapJsonObjectnull, array, and scalar values into their native Python equivalents.