llm_json_stream 0.2.2 copy "llm_json_stream: ^0.2.2" to clipboard
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 #

Tests Passing Dart pub package

Build ChatGPT-style streaming UIs in Flutter - Parse JSON reactively as LLM responses arrive, character-by-character.

Hero demo showing a Flutter app with streaming text appearing word-by-word in real-time, with the title "LLM JSON Stream Parser is Great!".

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

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 G in 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.

Side-by-side comparison. LEFT: "Traditional Approach" - Loading spinner, then suddenly all content appears at once. RIGHT: "With LLM JSON Stream" - Content streams in smoothly, text appears word-by-word.

Key Features #

1. Streaming String Values #

Display text as the LLM generates it, creating a responsive "typing" effect.

Streaming string property example

// 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 title field triggers a callback.
  • The stream prints 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.

Reactive list example

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:

Split screen showing two properties from same JSON. LEFT is a stream showing incremental text updates. RIGHT is a future

// 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);
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.

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:

  1. Check existing issues
  2. Open an issue before major changes
  3. Ensure tests pass (dart test)
  4. 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!

4
likes
160
points
284
downloads

Publisher

unverified uploader

Weekly Downloads

A streaming JSON parser optimized for LLM responses. Parse JSON reactively as it streams in, with path-based subscriptions and type-safe property access.

Repository (GitHub)
View/report issues

Topics

#json #streaming #parser #llm #reactive

Documentation

API reference

License

MIT (license)

Dependencies

meta

More

Packages that depend on llm_json_stream