From 69757eababa3dd576af18399bb2cb6f198d55d24 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Tue, 15 Sep 2026 00:15:42 -0400 Subject: [PATCH] Split JSON frontmatter at the complete object boundary --- frontmatter/default_handlers.py | 12 +++++++++--- tests/test_nested_json.py | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 tests/test_nested_json.py diff --git a/frontmatter/default_handlers.py b/frontmatter/default_handlers.py index 8c5d8ec..4139d21 100644 --- a/frontmatter/default_handlers.py +++ b/frontmatter/default_handlers.py @@ -283,9 +283,15 @@ class JSONHandler(BaseHandler): END_DELIMITER = "" def split(self, text: str) -> tuple[str, str]: - assert self.FM_BOUNDARY is not None - _, fm, content = self.FM_BOUNDARY.split(text, 2) - return "{" + fm + "}", content + try: + _, end = json.JSONDecoder().raw_decode(text) + except json.JSONDecodeError: + # Preserve existing malformed-frontmatter behavior: load() raises + # for invalid JSON with delimiters; missing boundaries stay content. + assert self.FM_BOUNDARY is not None + _, fm, content = self.FM_BOUNDARY.split(text, 2) + return "{" + fm + "}", content + return text[:end], text[end:] def load(self, fm: str, **kwargs: object) -> Any: return json.loads(fm, **kwargs) # type: ignore[arg-type] diff --git a/tests/test_nested_json.py b/tests/test_nested_json.py new file mode 100644 index 0000000..79551ae --- /dev/null +++ b/tests/test_nested_json.py @@ -0,0 +1,24 @@ +import json + +import pytest + +import frontmatter +from frontmatter.default_handlers import JSONHandler + + +@pytest.mark.parametrize('metadata', [ + {'outer': {'inner': 1}, 'title': 'Example'}, + {'items': [{'title': 'first'}, {'title': 'second'}]}, + {'quoted': '} and {', 'nested': {'text': 'escaped " quote'}}, +]) +def test_nested_unindented_json_round_trip(metadata): + post = frontmatter.Post('Body\n\n{content stays content}', JSONHandler(), **metadata) + text = frontmatter.dumps(post, indent=0) + restored = frontmatter.loads(text) + assert restored.metadata == metadata + assert restored.content == post.content + + +def test_malformed_delimited_json_still_raises(): + with pytest.raises(json.JSONDecodeError): + frontmatter.loads('{\n"title": invalid\n}\n\nBody')