llm_json_stream 0.2.2
llm_json_stream: ^0.2.2 copied to clipboard
A streaming JSON parser optimized for LLM responses. Parse JSON reactively as it streams in, with path-based subscriptions and type-safe property access.
LLM JSON Stream #
Build ChatGPT-style streaming UIs in Flutter - Parse JSON reactively as LLM responses arrive, character-by-character.

Visit the live demo at: https://comsindeed.github.io/json_stream_parser_demo/
Basic Usage #
import 'package:llm_json_stream/json_stream_parser.dart';
void main() async {
final stream = AI.sendMessage("Generate me JSON with ...");
final parser = JsonStreamParser(stream);
// Subscribe to specific properties
final titleStream = parser.getStringProperty("title");
final itemsStream = parser.getListProperty("items");
// React to values as they complete
titleStream.stream.listen((chunk) {
print("Title chunk: $chunk");
});
// Await full values when needed
final items = await itemsStream.future;
print("Full items list received: $items");
}
The Problem #
When streaming JSON from LLM APIs (OpenAI, Claude, Gemini), you receive incomplete chunks:
![LLM JSON chunks arriving incomplete: Chunk 1: {"title": "My G, Chunk 2: reat Blog Po, Chunk 3: st", "items": [, Chunk 4: {"id": 1, "n, Chunk 5: ame": "Item 1"}, Chunk 6: ]} - showing how traditional JSON parsers break on partial objects](https://rawgit.flutter-io.cn/ComsIndeed/llm_json_stream/main/assets/demo/problem.gif)
Standard parsers like jsonDecode() can't handle this. You're forced to either:
- Wait for the entire response (slow, high latency, defeats streaming)
- Display raw chunks (broken text like
{"title": "My Gin your UI) - Build a custom parser (complex, error-prone, time-consuming)
The Solution #
LLM JSON Stream parses JSON character-by-character as it arrives, letting you build reactive UIs that update instantly.

Key Features #
1. Streaming String Values #
Display text as the LLM generates it, creating a responsive "typing" effect.

// Create an instance
final parser = JsonStreamParser(llmStream);
// Access the `title` property as a streaming string
final titleProperty = parser.getStringProperty('title');
// Listen for incremental chunks as they arrive
titleProp.stream.listen((chunk) {
print('Title chunk: $chunk');
});
What happens
- Each fragment of the JSON containing the
titlefield triggers a callback. - The
streamprints incremental pieces
This demonstrates how getStringProperty().stream lets your UI update character‑by‑character while still providing a convenient way to get the final value.
2. Reactive Lists #
Add list items to your UI the moment they start parsing - before their content even arrives.

parser.list('tags').onElement((tag, index) {
// Fires IMMEDIATELY when "[{" is detected
setState(() {
tags.add(ArticleCard(index: index)); // Add placeholder
});
// Fill in content as it arrives
tag.asMap.str('title').stream.listen((chunk) {
setState(() => tags[index].title += chunk);
});
});
The magic: Traditional parsers wait for complete objects before updating your list, causing jarring jumps. This parser lets you add elements instantly and populate them reactively.
3. 🔀 Dual Stream & Future API #
Choose the right tool for each property:

// Stream for incremental updates
parser.str('email').stream.listen((chunk) {
displayText += chunk; // Real-time updates
});
// Future for atomic values
final id = await parser.number('id').future;
final isPublished = await parser.bool('published').future;
API Overview #
Parser Creation #
final parser = JsonStreamParser(streamFromLLM);
Short Aliases (Recommended) #
parser.str(path) // String property
parser.number(path) // Number property
parser.bool(path) // Boolean property
parser.list(path) // List property
parser.map(path) // Map (object) property
Long Form (For Clarity) #
parser.getStringProperty(path)
parser.getNumberProperty(path)
parser.getBooleanProperty(path)
parser.getListProperty(path)
parser.getMapProperty(path)
parser.getNullProperty(path)
Smart Casts #
Clean up nested property access:
// Instead of checking types manually
if (element is MapPropertyStream) {
element.getStringProperty('name')...
}
// Use smart casts
element.asMap.str('name').stream.listen(...);
Available casts: .asMap, .asList, .asStr, .asNum, .asBool
Path Syntax #
Access nested properties with dot notation or array indices:
![Various path examples with code and JSON side by side. Highlight how each path resolves. Examples: "user.name", "items[0].title", "data.users[2].profile.email". Show the parser successfully extracting each value as JSON streams in.](https://rawgit.flutter-io.cn/ComsIndeed/llm_json_stream/main/assets/demo/deep-future.gif)
parser.str('title') // Root property
parser.str('user.name') // Nested property
parser.str('items[0].title') // Array element
parser.number('data.users[2].age') // Deep nesting
Chainable API #
For better type safety and readability:
final user = parser.map('user');
final name = user.str('name');
final email = user.str('email');
final age = user.number('age');
Installation #
dependencies:
llm_json_stream: ^0.2.2
Then run:
dart pub get
# or
flutter pub get
API Reference #
JsonStreamParser #
| Method | Returns | Description |
|---|---|---|
str(path) |
StringPropertyStream |
Get string property |
number(path) |
NumberPropertyStream |
Get number property |
bool(path) |
BooleanPropertyStream |
Get boolean property |
list(path) |
ListPropertyStream |
Get list property |
map(path) |
MapPropertyStream |
Get map property |
dispose() |
Future<void> |
Clean up resources |
PropertyStream API #
All property streams provide:
.stream- Stream of values/chunks as they arrive.future- Future that completes with the final value
ListPropertyStream Special #
void onElement(void Function(PropertyStream element, int index) callback)
Fires when each array element starts parsing (before completion).
Contributing #
Contributions are welcome! Please:
- Check existing issues
- Open an issue before major changes
- Ensure tests pass (
dart test) - Follow existing code style
Support #
License #
MIT License - see LICENSE file for details.
Acknowledgments #
Built with ❤️ for the Flutter and Dart community. Special thanks to all contributors and early adopters providing feedback.
If this package helped you build something cool, consider:
- ⭐ Starring the repo
- 📝 Writing about your use case
- 🐛 Reporting bugs
- 💡 Suggesting features
Status: ⚠️ Early release (v0.2.1) - API may evolve based on feedback. 338 tests passing. Production-ready for adventurous developers!