{"id":29562965,"url":"https://github.com/duit-foundation/chunk_norris","last_synced_at":"2026-04-18T01:32:02.241Z","repository":{"id":304967798,"uuid":"1017471163","full_name":"Duit-Foundation/chunk_norris","owner":"Duit-Foundation","description":"That’s not a kick… THIS is a kick! A simple pure Dart library for working with chunked JSON","archived":false,"fork":false,"pushed_at":"2025-07-16T12:41:14.000Z","size":111,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-17T14:25:16.593Z","etag":null,"topics":["chunking","dart","flutter","json","sse"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Duit-Foundation.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-10T15:26:01.000Z","updated_at":"2025-07-16T11:59:53.000Z","dependencies_parsed_at":"2025-07-17T17:46:05.379Z","dependency_job_id":"01491270-a6b1-43f1-83f9-5d06897af633","html_url":"https://github.com/Duit-Foundation/chunk_norris","commit_stats":null,"previous_names":["duit-foundation/chunk_norris"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Duit-Foundation/chunk_norris","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Duit-Foundation%2Fchunk_norris","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Duit-Foundation%2Fchunk_norris/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Duit-Foundation%2Fchunk_norris/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Duit-Foundation%2Fchunk_norris/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Duit-Foundation","download_url":"https://codeload.github.com/Duit-Foundation/chunk_norris/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Duit-Foundation%2Fchunk_norris/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279000639,"owners_count":26082879,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chunking","dart","flutter","json","sse"],"created_at":"2025-07-18T18:01:25.324Z","updated_at":"2025-10-09T00:39:43.284Z","avatar_url":"https://github.com/Duit-Foundation.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chunk Norris 🥋 [![Coverage Status](https://coveralls.io/repos/github/Duit-Foundation/chunk_norris/badge.svg?branch=main)](https://coveralls.io/github/Duit-Foundation/chunk_norris?branch=main)\n\n\u003e **Chuck Norris doesn't wait for JSON to load. JSON loads instantly when Chuck Norris needs it.**\n\u003e\n\u003e That's exactly what this library does - it loads JSON so fast, it's almost supernatural!\n\n---\n\n**Chunk Norris** is a powerful Dart library for progressive JSON hydration. It allows you to work with JSON data that arrives in chunks, using placeholders that get resolved as data becomes available. Perfect for streaming APIs, Server-Sent Events, and any scenario where you need to handle partial data loading.\n\n## 🚀 Features\n\n- **Progressive Loading**: Load JSON data incrementally as chunks arrive\n- **Placeholder System**: Use `$1`, `$2` (or custom placeholder patterns), etc. as placeholders that get resolved dynamically\n- **Type Safety**: Strongly typed access to your data with automatic deserialization\n- **Streaming Support**: Built-in support for data streams (SSE, WebSockets, etc.)\n- **State Management**: Track loading states (pending, loaded, error) for each chunk\n- **Flexible API**: Work with raw JSON or strongly typed objects\n- **Error Handling**: Comprehensive error handling with fallback mechanisms\n\n## 📦 Installation\n\nAdd this to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n  chunk_norris: ^1.0.0\n```\n\n## 🎯 Quick Start\n\n### Basic ChunkJson Usage\n\n```dart\nimport 'package:chunk_norris/chunk_norris.dart';\n\nvoid main() async {\n  // Initialize with JSON containing placeholders\n  final chunkJson = ChunkJson.fromJson({\n    'name': 'John Doe',\n    'age': 30,\n    'address': '\\$1',  // Placeholder for chunk with ID \"1\"\n    'metadata': '\\$2', // Placeholder for chunk with ID \"2\"\n  });\n\n  // Listen for updates\n  chunkJson.listenUpdateStream((updatedData) {\n    print('Data updated: $updatedData');\n  });\n\n  // Process incoming chunks\n  await Future.delayed(Duration(seconds: 1));\n  chunkJson.processChunk({\n    '1': {'street': '123 Main St', 'city': 'Anytown'}\n  });\n\n  await Future.delayed(Duration(seconds: 1));\n  chunkJson.processChunk({\n    '2': {'source': 'api', 'timestamp': '2024-01-01T12:00:00Z'}\n  });\n\n  // Wait for all chunks to load\n  final resolvedData = await chunkJson.waitForAllData();\n  print('All chunks loaded: $resolvedData');\n\n  // Clean up\n  chunkJson.dispose();\n}\n```\n\n### Typed ChunkObject Usage\n\n```dart\nimport 'package:chunk_norris/chunk_norris.dart';\n\nclass User {\n  final String name;\n  final int age;\n  final Address address;\n  final Map\u003cString, dynamic\u003e metadata;\n\n  User({\n    required this.name,\n    required this.age,\n    required this.address,\n    required this.metadata,\n  });\n\n  factory User.fromJson(Map\u003cString, dynamic\u003e json) =\u003e User(\n    name: json['name'],\n    age: json['age'],\n    address: Address.fromJson(json['address']),\n    metadata: json['metadata'],\n  );\n}\n\nclass Address {\n  final String street;\n  final String city;\n\n  Address({required this.street, required this.city});\n  \n  factory Address.fromJson(Map\u003cString, dynamic\u003e json) =\u003e Address(\n    street: json['street'],\n    city: json['city'],\n  );\n}\n\nvoid main() async {\n  final userObject = ChunkObject.fromJson(\n    {\n      'name': 'John Doe',\n      'age': 30,\n      'address': '\\$1',\n      'metadata': '\\$2',\n    },\n    User.fromJson,\n    chunkFields: {\n      'address': ChunkField.object\u003cAddress\u003e('1', Address.fromJson),\n      'metadata': ChunkField('2', (data) =\u003e data as Map\u003cString, dynamic\u003e),\n    },\n  );\n\n  // userObject.listenChunkStates(onData) listen chunk state change\n  // userObject.listenChunkUpdate(onData) listen chunk update with resolved data\n  // userObject.listenRawChunkUpdate(onData) listen chunk update with raw data\n  // userObject.listenObjectUpdate(onData) listen root object update\n\n  // listen when all chunks resolved\n  userObject.listenObjectResolve(\n    (user) {\n      print(\"All chunks resolved!\");\n    },\n    onError: (error) {\n      print(\"Error: $error\");\n    },\n    onDone: () {\n      print(\"Stream is done\");\n    },\n  );\n\n\n  // Wait for complete object resolution\n  await userObject.waitForData();\n  final user = userObject.getData();\n  print('User fully loaded: ${user.name}, ${user.address.street}');\n\n  // Process chunks\n  userObject.processChunk({\n    '1': {'street': '123 Main St', 'city': 'Anytown'}\n  });\n\n  userObject.processChunk({\n    '2': {'source': 'api', 'timestamp': '2024-01-01T12:00:00Z'}\n  });\n\n  // Wait for all data\n  await userObject.waitForData();\n  final user = userObject.getData();\n  print('Final user: $user');\n\n  userObject.dispose();\n}\n```\n\n## 🔧 Advanced Features\n\n### Working with ChunkField\n\n`ChunkField` provides type-safe access to chunked data with built-in deserializers:\n\n```dart\n// Built-in field types\nfinal stringField = ChunkField.string('1');\nfinal intField = ChunkField.integer('2');\nfinal doubleField = ChunkField.decimal('3');\nfinal boolField = ChunkField.boolean('4');\nfinal listField = ChunkField.list\u003cString\u003e('5', (item) =\u003e item.toString());\nfinal objectField = ChunkField.object\u003cUser\u003e('6', User.fromJson);\n\n// Custom deserializer\nfinal customField = ChunkField\u003cDateTime\u003e('7', (data) {\n  return DateTime.parse(data.toString());\n});\n\n// Check field state\nprint('Field state: ${stringField.state}');\nprint('Is resolved: ${stringField.isResolved}');\nprint('Has error: ${stringField.hasError}');\n\n// Get value (throws if not loaded)\nfinal value = stringField.value;\n\n// Get value safely\nfinal safeValue = stringField.valueOrNull;\n\n// Wait for value\nfinal futureValue = await stringField.future;\n```\n\n### Streaming Data Processing\n\n```dart\n// Process streaming data (e.g., Server-Sent Events)\nfinal chunkJson = ChunkJson.fromJson(initialData);\n\n// Convert SSE stream to chunk stream\nfinal sseStream = EventSource('/api/stream');\nfinal chunkStream = sseStream.map((event) =\u003e event.data);\n\n// Process the stream\nchunkJson.processChunkStream(chunkStream);\n```\n\n### State Management\n\n```dart\nfinal chunkJson = ChunkJson.fromJson(dataWithPlaceholders);\n\n// Check individual key states\nfinal keyState = chunkJson.getKeyState('address');\nprint('Address loading state: $keyState');\n\n// Check if all chunks are resolved\nprint('All loaded: ${chunkJson.allChunksResolved}');\n\n// Wait for specific value\nfinal address = await chunkJson.getValueAsync('address');\n```\n\n### Error Handling\n\n```dart\nfinal chunkObject = ChunkObject.fromJson(\n  initialData,\n  User.fromJson,\n  chunkFields: chunkFields,\n);\n\n// Handle errors during processing\ntry {\n  await chunkObject.waitForData();\n  final user = chunkObject.getData();\n  print('Success: $user');\n} catch (error) {\n  print('Error: $error');\n}\n\n// Handle partial data\nfinal partialUser = chunkObject.getDataOrNull();\nif (partialUser != null) {\n  print('Partial data available: $partialUser');\n}\n```\n\n## 🎛️ API Reference\n\n### ChunkJson\n\n| Method | Description |\n|--------|-------------|\n| `ChunkJson.fromJson(Map\u003cString, dynamic\u003e json)` | Create instance from JSON with placeholders |\n| `getValue(String key)` | Get resolved value for key |\n| `getValueAsync(String key)` | Get Future for value |\n| `getKeyState(String key)` | Get loading state for key |\n| `getResolvedData()` | Get fully resolved JSON |\n| `processChunk(Map\u003cString, dynamic\u003e chunk)` | Process incoming chunk |\n| `processChunkStream(Stream\u003cString\u003e stream)` | Process chunk stream |\n| `waitForAllData()` | Wait for all chunks to load |\n| `listenUpdateStream(callback)` | Listen for data updates |\n| `allChunksResolved` | Check if all chunks are loaded |\n| `dispose()` | Clean up resources |\n\n### ChunkObject\u003cT\u003e\n\n| Method | Description |\n|--------|-------------|\n| `ChunkObject.fromJson(json, deserializer, {chunkFields})` | Create typed object instance |\n| `getData()` | Get fully resolved object (throws if not ready) |\n| `getDataOrNull()` | Get object or null if not ready |\n| `processChunk(Map\u003cString, dynamic\u003e chunk)` | Process incoming chunk |\n| `waitForData()` | Wait for all data to load |\n| `allChunksResolved` | Check if all chunks are loaded |\n| `getChunkField\u003cV\u003e(String key)` | Get typed chunk field by key |\n| `isFieldReady(String fieldKey)` | Check if specific field is ready |\n| `getFieldState(String fieldKey)` | Get state of specific field |\n| `getFieldError(String fieldKey)` | Get field error (if any) |\n| `chunkFields` | Get unmodifiable map of chunk fields |\n\n### ChunkField\u003cT\u003e\n\n| Property/Method | Description |\n|----------------|-------------|\n| `state` | Current loading state |\n| `isResolved` | Whether field is loaded |\n| `hasError` | Whether field has error |\n| `value` | Get resolved value (throws if not ready) |\n| `valueOrNull` | Get resolved value or null |\n| `future` | Future that completes when loaded |\n| `resolve(data)` | Resolve field with data |\n\n### ChunkState\n\n| State | Description |\n|-------|-------------|\n| `ChunkState.pending` | Data not yet loaded |\n| `ChunkState.loaded` | Data successfully loaded |\n| `ChunkState.error` | Error occurred during loading |\n\n## 🎨 Custom Placeholder Patterns\n\nBy default, Chunk Norris uses the pattern `$\u003cid\u003e` for placeholders (e.g., `$123`, `$456`). You can customize this pattern by providing a custom RegExp to match your specific needs.\n\n### Using Custom Patterns\n\n```dart\n// Default pattern: $123, $456, etc.\nfinal chunkJson = ChunkJson.fromJson({\n  'user': '$123',\n  'posts': '$456',\n});\n\n// Custom pattern: {id:123}, {id:456}, etc.\nfinal customChunkJson = ChunkJson.fromJson({\n  'user': '{id:123}',\n  'posts': '{id:456}',\n}, placeholderPattern: RegExp(r'^\\{id:(\\d+)\\}$'));\n\n// Custom pattern: @var123, @var456, etc.\nfinal varChunkJson = ChunkJson.fromJson({\n  'user': '@var123',\n  'posts': '@var456',\n}, placeholderPattern: RegExp(r'^@var(\\d+)$'));\n```\n\n### Custom Patterns with ChunkObject\n\n```dart\nfinal userObject = ChunkObject.fromJson(\n  {\n    'name': '{id:123}',\n    'email': '{id:456}',\n    'profile': '{id:789}',\n  },\n  (json) =\u003e User.fromJson(json),\n  placeholderPattern: RegExp(r'^\\{id:(\\d+)\\}$'),\n  chunkFields: {\n    'name': ChunkField.string('123'),\n    'email': ChunkField.string('456'),\n    'profile': ChunkField.object\u003cUserProfile\u003e('789', UserProfile.fromJson),\n  },\n);\n```\n\n### Pattern Requirements\n\nYour custom RegExp pattern must:\n\n- Include exactly one capture group `()` for the placeholder ID\n- Match the complete placeholder string (use `^` and `$` anchors)\n- Extract a unique identifier from the first capture group\n\n### Pattern Examples\n\n| Pattern | RegExp | Matches | Extracts |\n|---------|--------|---------|----------|\n| Default | `^\\$(\\d+)$` | `$123`, `$456` | `123`, `456` |\n| Braces | `^\\{id:(\\d+)\\}$` | `{id:123}`, `{id:456}` | `123`, `456` |\n| Variables | `^@var(\\d+)$` | `@var123`, `@var456` | `123`, `456` |\n| Custom | `^placeholder_(\\w+)$` | `placeholder_user`, `placeholder_posts` | `user`, `posts` |\n\n## 🌟 Use Cases\n\n### Server-Sent Events, WebSockets (SSE, WS)\n\nPerfect for real-time data streaming where the initial response contains a skeleton and subsequent events fill in the details.\n\n### Progressive Web Apps\n\nLoad critical data first, then enhance with additional information as it becomes available.\n\n### API Optimization\n\nReduce initial response times by sending partial data immediately and streaming the rest.\n\n### Data Aggregation from Multiple Sources\n\nCombine data from different sources (databases, APIs, files) into a single unified model. Load available data immediately and fill in missing pieces as they become available from slower sources.\n\n## 🤝 Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## 📝 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n---\n\n**Remember**: When Chuck Norris needs JSON data, it loads instantly. When you need JSON data, use Chunk Norris! 🥋\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fduit-foundation%2Fchunk_norris","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fduit-foundation%2Fchunk_norris","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fduit-foundation%2Fchunk_norris/lists"}