diff --git a/lib/galaxy/tool_util/verify/asserts/__init__.py b/lib/galaxy/tool_util/verify/asserts/__init__.py
index 8ffe4babaa1..f37bc018e09 100644
--- a/lib/galaxy/tool_util/verify/asserts/__init__.py
+++ b/lib/galaxy/tool_util/verify/asserts/__init__.py
@@ -9,7 +9,7 @@ from galaxy.util import unicodify
log = logging.getLogger(__name__)
-assertion_module_names = ["text", "tabular", "xml", "hdf5", "archive", "size"]
+assertion_module_names = ["text", "tabular", "xml", "json", "hdf5", "archive", "size"]
# Code for loading modules containing assertion checking functions, to
# create a new module of assertion functions, create the needed python
diff --git a/lib/galaxy/tool_util/verify/asserts/json.py b/lib/galaxy/tool_util/verify/asserts/json.py
new file mode 100644
index 00000000000..1ba151ba26c
--- /dev/null
+++ b/lib/galaxy/tool_util/verify/asserts/json.py
@@ -0,0 +1,53 @@
+import json
+from typing import (
+ Any,
+ Callable,
+ cast,
+)
+
+PropertyVisitor = Callable[[str, Any], Any]
+
+
+def any_in_tree(f: PropertyVisitor, json_tree: Any):
+ if isinstance(json_tree, list):
+ for element in json_tree:
+ if any_in_tree(f, element):
+ return True
+
+ elif isinstance(json_tree, dict):
+ for key, value in json_tree.items():
+ if f(cast(str, key), value):
+ return True
+ elif any_in_tree(f, value):
+ return True
+
+ return False
+
+
+def assert_has_json_property_with_value(
+ output,
+ property: str,
+ value: str,
+):
+ """Assert JSON tree contains the specified property with specified JSON-ified value."""
+ output_json = json.loads(output)
+ expected_value = json.loads(value)
+
+ def is_property(key, value):
+ return key == property and value == expected_value
+
+ assert any_in_tree(is_property, output_json), f"Failed to find property [{property}] with JSON value [{value}]"
+
+
+def assert_has_json_property_with_text(
+ output,
+ property: str,
+ text: str,
+):
+ """Assert JSON tree contains the specified property with specified JSON-ified value."""
+ output_json = json.loads(output)
+
+ def is_property(key, value):
+ return key == property and value == text
+
+ assert any_in_tree(is_property, output_json), f"Failed to find property [{property}] with text [{text}]"
diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd
index c4115d9e717..4b9e1bad82b 100644
--- a/lib/galaxy/tool_util/xsd/galaxy.xsd
+++ b/lib/galaxy/tool_util/xsd/galaxy.xsd
@@ -2070,6 +2070,7 @@ module.
+
@@ -2126,6 +2127,15 @@ module.
+
+
+
+
+
+
+
+
+
@@ -2356,6 +2366,7 @@ $attribute_list::5
+
@@ -2607,6 +2618,46 @@ $attribute_list::5
+
+
+
+```
+
+$attribute_list::5
+]]>
+
+
+
+
+
+ JSON-ified value to search for. This will be converted from an XML string to JSON with Python's json.loads function.
+
+
+
+
+
+
+```
+
+$attribute_list::5
+]]>
+
+
+
+
+
+ Text value to search for.
+
+
+
+
+
+
+ JSON property to search the target for.
+
+
+
+
diff --git a/test/unit/tool_util/verify/test_asserts.py b/test/unit/tool_util/verify/test_asserts.py
index 6806f4cba37..a6519e57f70 100644
--- a/test/unit/tool_util/verify/test_asserts.py
+++ b/test/unit/tool_util/verify/test_asserts.py
@@ -216,6 +216,18 @@ XML_XML_ELEMENT = """
"""
+JSON_HAS_PROPERTY_WITH_VALUE = """
+
+
+
+"""
+
+JSON_HAS_PROPERTY_WITH_WITH = """
+
+
+
+"""
+
VALID_XML = """
BAR
@@ -227,6 +239,9 @@ VALID_XML = """
"""
INVALID_XML = ''
+VALID_SIMPLE_JSON = """{"foo": 5, "list": [{"textprop": "right"}]}"""
+
+
if h5py is not None:
with tempfile.NamedTemporaryFile(delete=False) as tmp:
h5name = tmp.name
@@ -693,6 +708,26 @@ TESTS = [
lambda x: "Text of element with path './/more': Did not expect text matching expression '(BA[RZ]|QUX)$' in output ('BAR')"
in x,
),
+ (
+ JSON_HAS_PROPERTY_WITH_VALUE.format(property="foo", value="5"),
+ VALID_SIMPLE_JSON,
+ lambda x: len(x) == 0,
+ ),
+ (
+ JSON_HAS_PROPERTY_WITH_VALUE.format(property="foo", value="6"),
+ VALID_SIMPLE_JSON,
+ lambda x: "Failed to find property [foo] with JSON value [6]" in x,
+ ),
+ (
+ JSON_HAS_PROPERTY_WITH_WITH.format(property="textprop", text="right"),
+ VALID_SIMPLE_JSON,
+ lambda x: len(x) == 0,
+ ),
+ (
+ JSON_HAS_PROPERTY_WITH_WITH.format(property="textprop", text="wrong"),
+ VALID_SIMPLE_JSON,
+ lambda x: "Failed to find property [textprop] with text [wrong]" in x,
+ ),
# test has_archive_member with zip
(
ARCHIVE_HAS_ARCHIVE_MEMBER.format(path="(\\./)?testdir/file1.txt", content_assert="", all="false"),
@@ -988,6 +1023,10 @@ TEST_IDS = [
"xml_element failure (due to n)",
"xml_element failure (due to min/max in combination with negate)",
"xml_element failure (due to subassertion)",
+ "has_json_property_with_value success",
+ "has_json_property_with_value failure",
+ "has_json_property_with_text success",
+ "has_json_property_with_text failure",
"has_archive_member zip",
"has_archive_member tar",
"has_archive_member non-archive",