streaming-json-parser 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aramis Facchinetti
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: streaming-json-parser
3
+ Version: 0.1.0
4
+ Summary: A streaming JSON parser that processes JSON data incrementally, handling partial states. Useful for incrementally parsing partial responses from streaming outputs of Large Language Models (LLMs).
5
+ Author-email: Aramis Facchinetti <aramis.facchinetti16@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/aramisfacchinetti/streaming-json-parser
8
+ Project-URL: Repository, https://github.com/aramisfacchinetti/streaming-json-parser
9
+ Keywords: streaming,json,parser,llm,large language model,incremental parsing
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Development Status :: 5 - Production/Stable
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: Text Processing
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=8.3.5; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # Streaming JSON Parser
30
+
31
+ ## Objective
32
+
33
+ This Python module implements a streaming JSON parser designed to process JSON data incrementally. The primary goal is to handle potentially incomplete JSON data streams, such as those produced by Large Language Models (LLMs), and return the current state of the parsed object at any time.
34
+
35
+ ## Requirements Subset
36
+
37
+ The parser is specifically designed for a subset of JSON where:
38
+
39
+ - Values consist solely of **strings** and **objects**.
40
+ - **Escape sequences** in strings are not expected (though the implementation handles them).
41
+ - **Duplicate keys** in objects are not expected (though the implementation may tolerate them, typically keeping the last value).
42
+
43
+ ## Features
44
+
45
+ - **Incremental Parsing:** Consumes JSON data in chunks via the `consume()` method.
46
+ - **Partial State Retrieval:** The `get()` method returns the currently parsed JSON object state, even if the input stream is incomplete.
47
+ - **Partial String Values:** Returns partial string values as they are received (e.g., `{"key": "val` is valid partial state).
48
+ - **Key Handling:** Keys are only included in the returned object once their value type (string or object start) is identified.
49
+ - **Robustness:** Attempts to parse standard JSON efficiently and falls back to a more lenient state-machine parser for incomplete or slightly non-standard input.
50
+ - **Non-Standard JSON:** Tolerates some non-standard features like unquoted keys and single-quoted strings.
51
+ - **Error Handling:** Attempts to recover from invalid characters or find the first valid JSON object within the buffer.
52
+ - **Support for Primitives & Arrays:** Although the requirements focused on strings and objects, the implementation also handles numbers, booleans, null, and arrays as values within objects.
53
+
54
+ ## Implementation Approach
55
+
56
+ 1. **Buffering:** The `consume()` method appends incoming data chunks to an internal string buffer after escaping potentially invalid control characters.
57
+ 2. **Parsing (`get()`):**
58
+ - The buffer is first cleaned by removing leading whitespace and any characters before the first `{`.
59
+ - It attempts parsing using `json.raw_decode` for speed and standard compliance. If a dictionary is successfully decoded, it's returned, and the consumed portion is removed from the buffer.
60
+ - If `raw_decode` fails (due to incomplete data, syntax errors, or non-standard features), it falls back to the `IterativeStateMachine`.
61
+ - The `IterativeStateMachine` parses the buffer character by character, maintaining state to handle nested structures, different value types (including non-standard ones like unquoted keys), and partial inputs.
62
+ - The `get()` method returns the dictionary parsed by either method and updates the buffer, removing the parsed object and any leading garbage before the _next_ potential object. If no complete object can be parsed, an empty dictionary is returned.
63
+
64
+ ## Assumptions and Extensions
65
+
66
+ The implementation makes the following assumptions or extends the requirements:
67
+
68
+ 1. **Handling of Additional Primitive Types:** Supports numbers (int, float), booleans (`true`, `false`), and `null` as values, beyond the specified strings and objects.
69
+ 2. **Handling of Arrays:** Supports JSON arrays (`[...]`) as values within objects and can parse them, although `get()` only returns top-level _objects_ (`dict`).
70
+ 3. **Non-Standard JSON Support:** Tolerates and parses:
71
+ - Unquoted object keys (e.g., `{key: "value"}`).
72
+ - Single-quoted strings (e.g., `{'key': 'value'}`).
73
+ 4. **Escape Sequence Handling:** Actively handles standard JSON escape sequences (e.g., `\n`, `\"`) and Unicode escapes (`\uXXXX`) within strings, although they were "not expected".
74
+ 5. **Control Character Handling:** Escapes invalid JSON control characters (U+0000 to U+001F) found _outside_ of strings in the input buffer using `\uXXXX` format during `consume`.
75
+ 6. **Error Recovery/Robustness:** Discards leading non-JSON data before the first `{` and attempts to parse the first valid object found. Handles multiple objects in the buffer sequentially across `get()` calls.
76
+ 7. **Duplicate Keys:** Does not explicitly prevent duplicate keys; standard Python dictionary behavior (last key wins) likely applies.
77
+ 8. **Efficiency Strategy:** Uses `json.raw_decode` first, falling back to a custom parser only when necessary.
78
+ 9. **Input Type:** `consume` expects string input; other types are ignored.
79
+
80
+ ## Algorithmic Complexity
81
+
82
+ The efficiency of the `StreamingJsonParser` depends on the method being called and the nature of the input data stream.
83
+
84
+ - **`consume(buffer: str)`:**
85
+
86
+ - **Time Complexity:** Primarily involves appending the new `buffer` (length `k`) to the internal buffer and performing basic character escaping. This is typically **O(k)**. String concatenation in Python can sometimes be O(N+k) where N is the current buffer size, but often optimized closer to O(k) amortized.
87
+ - **Space Complexity:** Increases the internal buffer size by O(k).
88
+
89
+ - **`get()`:**
90
+
91
+ - **Time Complexity:**
92
+ - **Fast Path (`json.raw_decode`):** If the buffer starts with a complete, standard JSON object of size `P`, Python's built-in decoder is used. This is generally efficient, expected to be around **O(P)**.
93
+ - **Fallback Path (`IterativeStateMachine`):** If `raw_decode` fails (due to incomplete data or non-standard syntax), the custom state machine parses the buffer character by character. In the worst case, it might need to scan a significant portion of the buffer (size `B'`). The complexity is dominated by this scan and subsequent buffer slicing, making it roughly **O(B')**.
94
+ - **Overall:** The complexity varies. It's close to O(P) when complete objects are readily available and standard, and approaches O(B') when parsing incomplete or non-standard streams requires the iterative fallback.
95
+ - **Space Complexity:** Does not inherently allocate significant additional space beyond the internal representation of the parsed object being returned. The main space usage comes from the internal buffer managed by `consume`.
96
+
97
+ - **Overall Space Complexity:** The primary factor is the internal buffer. In the worst case (e.g., a very large stream is consumed without any complete objects being parsed and removed by `get()`), the space complexity can be **O(T)**, where T is the total size of the streamed data received so far. In typical usage where `get()` successfully parses and removes objects, the buffer size stays manageable.
98
+
99
+ ## Usage
100
+
101
+ ```python
102
+ # Import the class
103
+ from streaming_json_parser import StreamingJsonParser
104
+
105
+ # Initialize the parser
106
+ parser = StreamingJsonParser()
107
+
108
+ # Consume JSON data chunks
109
+ parser.consume('{"name": "Example", "data": {"val') # Partial object value
110
+ parser.consume('ue": "stream"}') # Complete the object
111
+
112
+ # Get the current state of the parsed object
113
+ # This will return the first complete object found.
114
+ current_object = parser.get()
115
+ print(current_object)
116
+ # Output: {'name': 'Example', 'data': {'value': 'stream'}}
117
+
118
+ # The buffer is cleared/updated after get(), ready for the next object
119
+ parser.consume('{"next": "object"}')
120
+ next_object = parser.get()
121
+ print(next_object)
122
+ # Output: {'next': 'object'}
123
+
124
+ # Example with partial string value
125
+ parser = StreamingJsonParser()
126
+ parser.consume('{"key": "partial string')
127
+ partial_state = parser.get()
128
+ print(partial_state)
129
+ # Output: {'key': 'partial string'}
130
+
131
+ parser.consume(' complete"}')
132
+ complete_state = parser.get()
133
+ print(complete_state)
134
+ # Output: {'key': 'partial string complete'}
135
+ ```
136
+
137
+ ## Setup
138
+
139
+ To use this parser and run the tests, you need to install the dependencies:
140
+
141
+ ```bash
142
+ pip install -r requirements.txt
143
+ ```
144
+
145
+ The `requirements.txt` file includes:
146
+
147
+ - `pytest`
148
+ - `pytest-cov`
149
+
150
+ ## Testing
151
+
152
+ Unit tests are provided in `test_streaming_json_parser.py`. You can run them using `pytest`:
153
+
154
+ ```bash
155
+ pytest
156
+ ```
@@ -0,0 +1,128 @@
1
+ # Streaming JSON Parser
2
+
3
+ ## Objective
4
+
5
+ This Python module implements a streaming JSON parser designed to process JSON data incrementally. The primary goal is to handle potentially incomplete JSON data streams, such as those produced by Large Language Models (LLMs), and return the current state of the parsed object at any time.
6
+
7
+ ## Requirements Subset
8
+
9
+ The parser is specifically designed for a subset of JSON where:
10
+
11
+ - Values consist solely of **strings** and **objects**.
12
+ - **Escape sequences** in strings are not expected (though the implementation handles them).
13
+ - **Duplicate keys** in objects are not expected (though the implementation may tolerate them, typically keeping the last value).
14
+
15
+ ## Features
16
+
17
+ - **Incremental Parsing:** Consumes JSON data in chunks via the `consume()` method.
18
+ - **Partial State Retrieval:** The `get()` method returns the currently parsed JSON object state, even if the input stream is incomplete.
19
+ - **Partial String Values:** Returns partial string values as they are received (e.g., `{"key": "val` is valid partial state).
20
+ - **Key Handling:** Keys are only included in the returned object once their value type (string or object start) is identified.
21
+ - **Robustness:** Attempts to parse standard JSON efficiently and falls back to a more lenient state-machine parser for incomplete or slightly non-standard input.
22
+ - **Non-Standard JSON:** Tolerates some non-standard features like unquoted keys and single-quoted strings.
23
+ - **Error Handling:** Attempts to recover from invalid characters or find the first valid JSON object within the buffer.
24
+ - **Support for Primitives & Arrays:** Although the requirements focused on strings and objects, the implementation also handles numbers, booleans, null, and arrays as values within objects.
25
+
26
+ ## Implementation Approach
27
+
28
+ 1. **Buffering:** The `consume()` method appends incoming data chunks to an internal string buffer after escaping potentially invalid control characters.
29
+ 2. **Parsing (`get()`):**
30
+ - The buffer is first cleaned by removing leading whitespace and any characters before the first `{`.
31
+ - It attempts parsing using `json.raw_decode` for speed and standard compliance. If a dictionary is successfully decoded, it's returned, and the consumed portion is removed from the buffer.
32
+ - If `raw_decode` fails (due to incomplete data, syntax errors, or non-standard features), it falls back to the `IterativeStateMachine`.
33
+ - The `IterativeStateMachine` parses the buffer character by character, maintaining state to handle nested structures, different value types (including non-standard ones like unquoted keys), and partial inputs.
34
+ - The `get()` method returns the dictionary parsed by either method and updates the buffer, removing the parsed object and any leading garbage before the _next_ potential object. If no complete object can be parsed, an empty dictionary is returned.
35
+
36
+ ## Assumptions and Extensions
37
+
38
+ The implementation makes the following assumptions or extends the requirements:
39
+
40
+ 1. **Handling of Additional Primitive Types:** Supports numbers (int, float), booleans (`true`, `false`), and `null` as values, beyond the specified strings and objects.
41
+ 2. **Handling of Arrays:** Supports JSON arrays (`[...]`) as values within objects and can parse them, although `get()` only returns top-level _objects_ (`dict`).
42
+ 3. **Non-Standard JSON Support:** Tolerates and parses:
43
+ - Unquoted object keys (e.g., `{key: "value"}`).
44
+ - Single-quoted strings (e.g., `{'key': 'value'}`).
45
+ 4. **Escape Sequence Handling:** Actively handles standard JSON escape sequences (e.g., `\n`, `\"`) and Unicode escapes (`\uXXXX`) within strings, although they were "not expected".
46
+ 5. **Control Character Handling:** Escapes invalid JSON control characters (U+0000 to U+001F) found _outside_ of strings in the input buffer using `\uXXXX` format during `consume`.
47
+ 6. **Error Recovery/Robustness:** Discards leading non-JSON data before the first `{` and attempts to parse the first valid object found. Handles multiple objects in the buffer sequentially across `get()` calls.
48
+ 7. **Duplicate Keys:** Does not explicitly prevent duplicate keys; standard Python dictionary behavior (last key wins) likely applies.
49
+ 8. **Efficiency Strategy:** Uses `json.raw_decode` first, falling back to a custom parser only when necessary.
50
+ 9. **Input Type:** `consume` expects string input; other types are ignored.
51
+
52
+ ## Algorithmic Complexity
53
+
54
+ The efficiency of the `StreamingJsonParser` depends on the method being called and the nature of the input data stream.
55
+
56
+ - **`consume(buffer: str)`:**
57
+
58
+ - **Time Complexity:** Primarily involves appending the new `buffer` (length `k`) to the internal buffer and performing basic character escaping. This is typically **O(k)**. String concatenation in Python can sometimes be O(N+k) where N is the current buffer size, but often optimized closer to O(k) amortized.
59
+ - **Space Complexity:** Increases the internal buffer size by O(k).
60
+
61
+ - **`get()`:**
62
+
63
+ - **Time Complexity:**
64
+ - **Fast Path (`json.raw_decode`):** If the buffer starts with a complete, standard JSON object of size `P`, Python's built-in decoder is used. This is generally efficient, expected to be around **O(P)**.
65
+ - **Fallback Path (`IterativeStateMachine`):** If `raw_decode` fails (due to incomplete data or non-standard syntax), the custom state machine parses the buffer character by character. In the worst case, it might need to scan a significant portion of the buffer (size `B'`). The complexity is dominated by this scan and subsequent buffer slicing, making it roughly **O(B')**.
66
+ - **Overall:** The complexity varies. It's close to O(P) when complete objects are readily available and standard, and approaches O(B') when parsing incomplete or non-standard streams requires the iterative fallback.
67
+ - **Space Complexity:** Does not inherently allocate significant additional space beyond the internal representation of the parsed object being returned. The main space usage comes from the internal buffer managed by `consume`.
68
+
69
+ - **Overall Space Complexity:** The primary factor is the internal buffer. In the worst case (e.g., a very large stream is consumed without any complete objects being parsed and removed by `get()`), the space complexity can be **O(T)**, where T is the total size of the streamed data received so far. In typical usage where `get()` successfully parses and removes objects, the buffer size stays manageable.
70
+
71
+ ## Usage
72
+
73
+ ```python
74
+ # Import the class
75
+ from streaming_json_parser import StreamingJsonParser
76
+
77
+ # Initialize the parser
78
+ parser = StreamingJsonParser()
79
+
80
+ # Consume JSON data chunks
81
+ parser.consume('{"name": "Example", "data": {"val') # Partial object value
82
+ parser.consume('ue": "stream"}') # Complete the object
83
+
84
+ # Get the current state of the parsed object
85
+ # This will return the first complete object found.
86
+ current_object = parser.get()
87
+ print(current_object)
88
+ # Output: {'name': 'Example', 'data': {'value': 'stream'}}
89
+
90
+ # The buffer is cleared/updated after get(), ready for the next object
91
+ parser.consume('{"next": "object"}')
92
+ next_object = parser.get()
93
+ print(next_object)
94
+ # Output: {'next': 'object'}
95
+
96
+ # Example with partial string value
97
+ parser = StreamingJsonParser()
98
+ parser.consume('{"key": "partial string')
99
+ partial_state = parser.get()
100
+ print(partial_state)
101
+ # Output: {'key': 'partial string'}
102
+
103
+ parser.consume(' complete"}')
104
+ complete_state = parser.get()
105
+ print(complete_state)
106
+ # Output: {'key': 'partial string complete'}
107
+ ```
108
+
109
+ ## Setup
110
+
111
+ To use this parser and run the tests, you need to install the dependencies:
112
+
113
+ ```bash
114
+ pip install -r requirements.txt
115
+ ```
116
+
117
+ The `requirements.txt` file includes:
118
+
119
+ - `pytest`
120
+ - `pytest-cov`
121
+
122
+ ## Testing
123
+
124
+ Unit tests are provided in `test_streaming_json_parser.py`. You can run them using `pytest`:
125
+
126
+ ```bash
127
+ pytest
128
+ ```
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "streaming-json-parser"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name = "Aramis Facchinetti", email = "aramis.facchinetti16@gmail.com" },
10
+ ]
11
+ description = "A streaming JSON parser that processes JSON data incrementally, handling partial states. Useful for incrementally parsing partial responses from streaming outputs of Large Language Models (LLMs)."
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ license = { text = "MIT" }
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ "Development Status :: 5 - Production/Stable",
25
+ "Intended Audience :: Developers",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Topic :: Text Processing",
28
+ ]
29
+ keywords = ["streaming", "json", "parser", "llm", "large language model", "incremental parsing"]
30
+
31
+ # dependencies = [
32
+ # "some-package>=1.0",
33
+ # ]
34
+
35
+ [project.optional-dependencies]
36
+ test = [
37
+ "pytest>=8.3.5",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/aramisfacchinetti/streaming-json-parser"
42
+ Repository = "https://github.com/aramisfacchinetti/streaming-json-parser"
43
+
44
+ [tool.setuptools.packages.find]
45
+ where = ["src"]
46
+ include = ["streaming_json_parser*"]
47
+ exclude = ["tests*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+