-
Notifications
You must be signed in to change notification settings - Fork 44
feat: Add JSON indentation option to decode() method #37
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
e35585f
fd4c793
44ffada
9f906f9
41a8da5
aa992fd
25ca310
16bfa8f
7f854b5
239e9d2
28dc1ea
089fa56
f4e54ed
c7bdbcf
b53ca61
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -8,7 +8,8 @@ | |||||
| and validates array lengths and delimiters. | ||||||
| """ | ||||||
|
|
||||||
| from typing import Any, Dict, List, Optional, Tuple | ||||||
| import json | ||||||
| from typing import Any, Dict, List, Optional, Tuple, Union | ||||||
|
|
||||||
| from ._literal_utils import is_boolean_or_null_literal, is_numeric_literal | ||||||
| from ._parsing_utils import ( | ||||||
|
|
@@ -228,18 +229,42 @@ def split_key_value(line: str) -> Tuple[str, str]: | |||||
| return (key, value) | ||||||
|
|
||||||
|
|
||||||
| def decode(input_str: str, options: Optional[DecodeOptions] = None) -> JsonValue: | ||||||
| def decode(input_str: str, options: Optional[DecodeOptions] = None) -> Union[JsonValue, str]: | ||||||
| """Decode a TOON-formatted string to a Python value. | ||||||
|
|
||||||
| This function parses TOON format and returns the decoded data. By default, | ||||||
| it returns a Python object (dict, list, str, int, float, bool, or None). | ||||||
|
|
||||||
| The DecodeOptions.json_indent parameter is a Python-specific feature that | ||||||
| enables returning a JSON-formatted string instead of a Python object. | ||||||
| This is useful for applications that need pretty-printed JSON output. | ||||||
|
|
||||||
| Args: | ||||||
| input_str: TOON-formatted string | ||||||
| options: Optional decoding options | ||||||
| input_str: TOON-formatted string to decode | ||||||
| options: Optional DecodeOptions with indent, strict, and json_indent | ||||||
| settings. If not provided, defaults are used (indent=2, | ||||||
| strict=True, json_indent=None). | ||||||
|
|
||||||
| Returns: | ||||||
| Decoded Python value | ||||||
| By default (json_indent=None): Decoded Python value (object, array, | ||||||
| string, number, boolean, or null). | ||||||
| When json_indent is set: A JSON-formatted string with the specified | ||||||
| indentation level. Example: DecodeOptions(json_indent=2) returns | ||||||
| pretty-printed JSON with 2-space indentation. | ||||||
|
|
||||||
| Raises: | ||||||
| ToonDecodeError: If input is malformed | ||||||
| ToonDecodeError: If input is malformed or violates strict-mode rules | ||||||
| ValueError: If json_indent is negative | ||||||
|
|
||||||
| Example: | ||||||
| >>> toon = "name: Alice\\nage: 30" | ||||||
| >>> decode(toon) | ||||||
| {'name': 'Alice', 'age': 30} | ||||||
| >>> print(decode(toon, DecodeOptions(json_indent=2))) | ||||||
| { | ||||||
| "name": "Alice", | ||||||
| "age": 30 | ||||||
| } | ||||||
| """ | ||||||
| if options is None: | ||||||
| options = DecodeOptions() | ||||||
|
|
@@ -273,32 +298,44 @@ def decode(input_str: str, options: Optional[DecodeOptions] = None) -> JsonValue | |||||
| # Check for empty input (per spec Section 8: empty/whitespace-only → empty object) | ||||||
| non_blank_lines = [ln for ln in lines if not ln.is_blank] | ||||||
| if not non_blank_lines: | ||||||
| return {} | ||||||
|
|
||||||
| # Determine root form (Section 5) | ||||||
| first_line = non_blank_lines[0] | ||||||
|
|
||||||
| # Check if it's a root array header | ||||||
| header_info = parse_header(first_line.content) | ||||||
| if header_info is not None and header_info[0] is None: # No key = root array | ||||||
| # Root array | ||||||
| return decode_array(lines, 0, 0, header_info, strict) | ||||||
| result: Any = {} | ||||||
| else: | ||||||
| # Determine root form (Section 5) | ||||||
| first_line = non_blank_lines[0] | ||||||
|
|
||||||
| # Check if it's a root array header | ||||||
| header_info = parse_header(first_line.content) | ||||||
| if header_info is not None and header_info[0] is None: # No key = root array | ||||||
| # Root array | ||||||
| result = decode_array(lines, 0, 0, header_info, strict) | ||||||
| else: | ||||||
| # Check if it's a single primitive | ||||||
| if len(non_blank_lines) == 1: | ||||||
| line_content = first_line.content | ||||||
| # Check if it's not a key-value line | ||||||
| try: | ||||||
| split_key_value(line_content) | ||||||
| except ToonDecodeError: | ||||||
| # Not a key-value, check if it's a header | ||||||
| if header_info is None: | ||||||
| # Single primitive | ||||||
| result = parse_primitive(line_content) | ||||||
| else: | ||||||
| result = decode_object(lines, 0, 0, strict) | ||||||
| else: | ||||||
| # It's a key-value, so root object | ||||||
| result = decode_object(lines, 0, 0, strict) | ||||||
| else: | ||||||
| # Otherwise, root object | ||||||
| result = decode_object(lines, 0, 0, strict) | ||||||
|
|
||||||
| # Check if it's a single primitive | ||||||
| if len(non_blank_lines) == 1: | ||||||
| line_content = first_line.content | ||||||
| # Check if it's not a key-value line | ||||||
| try: | ||||||
| split_key_value(line_content) | ||||||
| # It's a key-value, so root object | ||||||
| except ToonDecodeError: | ||||||
| # Not a key-value, check if it's a header | ||||||
| if header_info is None: | ||||||
| # Single primitive | ||||||
| return parse_primitive(line_content) | ||||||
| # If json_indent is specified, return JSON-formatted string | ||||||
| if options.json_indent is not None: | ||||||
jreakin marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| if options.json_indent < 0: | ||||||
| raise ToonDecodeError(f"json_indent must be non-negative, got {options.json_indent}") | ||||||
|
||||||
| raise ToonDecodeError(f"json_indent must be non-negative, got {options.json_indent}") | |
| raise ValueError(f"json_indent must be non-negative, got {options.json_indent}") |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -52,12 +52,26 @@ class DecodeOptions: | |||||||||||||||
|
|
||||||||||||||||
| Attributes: | ||||||||||||||||
| indent: Number of spaces per indentation level (default: 2) | ||||||||||||||||
| Used for parsing TOON format. | ||||||||||||||||
| strict: Enable strict validation (default: True) | ||||||||||||||||
| Enforces spec conformance checks. | ||||||||||||||||
|
Comment on lines
54
to
+57
|
||||||||||||||||
| indent: Number of spaces per indentation level (default: 2) | |
| Used for parsing TOON format. | |
| strict: Enable strict validation (default: True) | |
| Enforces spec conformance checks. | |
| indent: Number of spaces per indentation level (default: 2). | |
| strict: Enable strict validation (default: True). | |
| Enforces TOON specification conformance checks. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,8 +10,12 @@ | |||||||||||||||||||||||||
| Python type normalization is tested in test_normalization.py. | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||
| from typing import Any, Dict, List, Tuple | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| from tests.test_spec_fixtures import get_all_decode_fixtures | ||||||||||||||||||||||||||
| from toon_format import ToonDecodeError, decode, encode | ||||||||||||||||||||||||||
| from toon_format.types import DecodeOptions, EncodeOptions | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -286,3 +290,118 @@ def test_roundtrip_with_length_marker(self): | |||||||||||||||||||||||||
| toon = encode(original, {"lengthMarker": "#"}) | ||||||||||||||||||||||||||
| decoded = decode(toon) | ||||||||||||||||||||||||||
| assert decoded == original | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # TODO: Add targeted unit tests for decode()'s json_indent feature here. | ||||||||||||||||||||||||||
| # See Issue #10. For now, comprehensive tests are in | ||||||||||||||||||||||||||
| # TestDecodeJSONIndentationWithSpecFixtures. | ||||||||||||||||||||||||||
| @pytest.mark.skip( | ||||||||||||||||||||||||||
| reason="Placeholder for targeted decode() JSON indentation tests. See TODO above." | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| class TestDecodeJSONIndentation: | ||||||||||||||||||||||||||
| """Test decode() JSON indentation feature (Issue #10). | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Comprehensive tests for the json_indent feature are in | ||||||||||||||||||||||||||
| TestDecodeJSONIndentationWithSpecFixtures, which validates against official | ||||||||||||||||||||||||||
| TOON specification fixtures. | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
Comment on lines
+298
to
+311
|
||||||||||||||||||||||||||
| @pytest.mark.skip( | |
| reason="Placeholder for targeted decode() JSON indentation tests. See TODO above." | |
| ) | |
| class TestDecodeJSONIndentation: | |
| """Test decode() JSON indentation feature (Issue #10). | |
| Comprehensive tests for the json_indent feature are in | |
| TestDecodeJSONIndentationWithSpecFixtures, which validates against official | |
| TOON specification fixtures. | |
| """ | |
| pass |
Copilot
AI
Nov 18, 2025
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 unpacking in this loop is incorrect. get_all_decode_fixtures() returns tuples of (test_id, test_data, fixture_name) (3 elements), but the code unpacks only 2 elements into (test_id, test_data). This will cause a ValueError: too many values to unpack at runtime.
Fix the unpacking to include all three elements:
for test_id, test_data, fixture_name in all_fixtures:
if f"{fixture_name}.json" in selected_files and len(test_cases) < 9:
test_cases.append((test_id, test_data))
Uh oh!
There was an error while loading. Please reload this page.