Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions piccolo/query/operators/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, Any, Optional, Union

from piccolo.querystring import QueryString
from piccolo.utils.encoding import dump_json
from piccolo.utils.encoding import dump_json, load_json

if TYPE_CHECKING:
from piccolo.columns.column_types import JSON
Expand All @@ -12,9 +12,17 @@
class JSONQueryString(QueryString):

def clean_value(self, value: Any):
if not isinstance(value, (str, QueryString)):
value = dump_json(value)
return value
if isinstance(value, QueryString):
return value

if isinstance(value, str):
try:
load_json(value)
except Exception:
value = dump_json(value)
return value

return dump_json(value)

def __eq__(self, value) -> QueryString: # type: ignore[override]
value = self.clean_value(value)
Expand Down
74 changes: 74 additions & 0 deletions tests/query/operators/test_json.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest import TestCase

from piccolo.columns import JSONB
from piccolo.querystring import QueryString
from piccolo.query.operators.json import GetChildElement, GetElementFromPath
from piccolo.table import Table
from tests.base import engines_skip
Expand Down Expand Up @@ -50,3 +51,76 @@ def test_query(self):
)

self.assertListEqual(query_args, [["a", "b"]])


class TestJSONQueryString(TestCase):

def test_clean_value(self):
element = GetChildElement(RecordingStudio.facilities, "name")

# Plain strings should be encoded as JSON
self.assertEqual(element.clean_value("hello world"), '"hello world"')
self.assertEqual(element.clean_value("foo"), '"foo"')

# Already valid JSON strings should be preserved
self.assertEqual(
element.clean_value('{"message": "hello world"}'),
'{"message": "hello world"}',
)
self.assertEqual(element.clean_value('["a", "b"]'), '["a", "b"]')
self.assertEqual(
element.clean_value('"already quoted"'),
'"already quoted"',
)

# Other python types should be dumped to JSON
self.assertEqual(
element.clean_value({"message": "hello world"}),
'{"message": "hello world"}',
)
self.assertEqual(element.clean_value(["a", "b"]), '["a", "b"]')
self.assertEqual(element.clean_value(123), "123")
self.assertEqual(element.clean_value(True), "true")
self.assertEqual(element.clean_value(None), "null")

# QueryString should be preserved
qs = QueryString("SELECT 1")
self.assertEqual(element.clean_value(qs), qs)

def test_eq_plain_string(self):
querystring = (
GetChildElement(RecordingStudio.facilities, "name")
== "hello world"
)
sql, query_args = querystring.compile_string(engine_type="postgres")
self.assertEqual(
sql,
'"recording_studio"."facilities" -> $1 = $2',
)
self.assertListEqual(query_args, ["name", '"hello world"'])

def test_ne_plain_string(self):
querystring = (
GetChildElement(RecordingStudio.facilities, "name")
!= "hello world"
)
sql, query_args = querystring.compile_string(engine_type="postgres")
self.assertEqual(
sql,
'"recording_studio"."facilities" -> $1 != $2',
)
self.assertListEqual(query_args, ["name", '"hello world"'])

def test_eq_valid_json_string(self):
querystring = (
GetChildElement(RecordingStudio.facilities, "info")
== '{"message": "hello world"}'
)
sql, query_args = querystring.compile_string(engine_type="postgres")
self.assertEqual(
sql,
'"recording_studio"."facilities" -> $1 = $2',
)
self.assertListEqual(
query_args, ["info", '{"message": "hello world"}']
)