Skip to content

Commit b5572c9

Browse files
authored
Merge pull request #1390 from kurtmckee/fix-json_path-str-rendering
Unambiguously quote and escape properties in JSON path rendering
2 parents 1145521 + 4cf45b9 commit b5572c9

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

jsonschema/exceptions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from textwrap import dedent, indent
99
from typing import TYPE_CHECKING, Any, ClassVar
1010
import heapq
11+
import re
1112
import warnings
1213

1314
from attrs import define
@@ -23,6 +24,8 @@
2324
WEAK_MATCHES: frozenset[str] = frozenset(["anyOf", "oneOf"])
2425
STRONG_MATCHES: frozenset[str] = frozenset()
2526

27+
_JSON_PATH_COMPATIBLE_PROPERTY_PATTERN = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$")
28+
2629
_unset = _utils.Unset()
2730

2831

@@ -152,8 +155,11 @@ def json_path(self) -> str:
152155
for elem in self.absolute_path:
153156
if isinstance(elem, int):
154157
path += "[" + str(elem) + "]"
155-
else:
158+
elif _JSON_PATH_COMPATIBLE_PROPERTY_PATTERN.match(elem):
156159
path += "." + elem
160+
else:
161+
escaped_elem = elem.replace("\\", "\\\\").replace("'", r"\'")
162+
path += "['" + escaped_elem + "']"
157163
return path
158164

159165
def _set(

jsonschema/tests/test_exceptions.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from unittest import TestCase
22
import textwrap
33

4+
import jsonpath_ng
5+
46
from jsonschema import exceptions
57
from jsonschema.validators import _LATEST_VERSION
68

@@ -700,3 +702,58 @@ class TestHashable(TestCase):
700702
def test_hashable(self):
701703
{exceptions.ValidationError("")}
702704
{exceptions.SchemaError("")}
705+
706+
707+
class TestJsonPathRendering(TestCase):
708+
def validate_json_path_rendering(self, property_name, expected_path):
709+
error = exceptions.ValidationError(
710+
path=[property_name],
711+
message="1",
712+
validator="foo",
713+
instance="i1",
714+
)
715+
716+
rendered_json_path = error.json_path
717+
self.assertEqual(rendered_json_path, expected_path)
718+
719+
re_parsed_name = jsonpath_ng.parse(rendered_json_path).right.fields[0]
720+
self.assertEqual(re_parsed_name, property_name)
721+
722+
def test_basic(self):
723+
self.validate_json_path_rendering("x", "$.x")
724+
725+
def test_empty(self):
726+
self.validate_json_path_rendering("", "$['']")
727+
728+
def test_number(self):
729+
self.validate_json_path_rendering("1", "$['1']")
730+
731+
def test_period(self):
732+
self.validate_json_path_rendering(".", "$['.']")
733+
734+
def test_single_quote(self):
735+
self.validate_json_path_rendering("'", r"$['\'']")
736+
737+
def test_space(self):
738+
self.validate_json_path_rendering(" ", "$[' ']")
739+
740+
def test_backslash(self):
741+
self.validate_json_path_rendering("\\", r"$['\\']")
742+
743+
def test_backslash_single_quote(self):
744+
self.validate_json_path_rendering(r"\'", r"$['\\\'']")
745+
746+
def test_underscore(self):
747+
self.validate_json_path_rendering("_", r"$['_']")
748+
749+
def test_double_quote(self):
750+
self.validate_json_path_rendering('"', """$['"']""")
751+
752+
def test_hyphen(self):
753+
self.validate_json_path_rendering("-", "$['-']")
754+
755+
def test_json_path_injection(self):
756+
self.validate_json_path_rendering("a[0]", "$['a[0]']")
757+
758+
def test_open_bracket(self):
759+
self.validate_json_path_rendering("[", "$['[']")

noxfile.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def tests(session, installable):
6161
env = dict(JSON_SCHEMA_TEST_SUITE=str(ROOT / "json"))
6262

6363
session.install("virtue", installable)
64+
session.install("jsonpath-ng", installable)
6465

6566
if session.posargs and session.posargs[0] == "coverage":
6667
if len(session.posargs) > 1 and session.posargs[1] == "github":

0 commit comments

Comments
 (0)