ParseJSONIterative parses JSON using the iterative parser Supports partial parsing for streaming scenarios Returns objects and arrays (matching llama.cpp behavior)
(s string, isPartial bool)
| 325 | // Supports partial parsing for streaming scenarios |
| 326 | // Returns objects and arrays (matching llama.cpp behavior) |
| 327 | func ParseJSONIterative(s string, isPartial bool) ([]map[string]any, error) { |
| 328 | parser := NewChatMsgParser(s, isPartial) |
| 329 | var results []map[string]any |
| 330 | |
| 331 | // Try to parse JSON values one by one |
| 332 | for parser.Pos() < len(parser.Input()) { |
| 333 | jsonValue, isPartialJSON, _, err := parser.TryConsumeJSON() |
| 334 | if err != nil { |
| 335 | // If it's a partial exception and we're in partial mode, return what we have |
| 336 | if _, ok := err.(*ChatMsgPartialException); ok && isPartial { |
| 337 | break |
| 338 | } |
| 339 | // For non-partial errors or when not in partial mode, try legacy parsing |
| 340 | return parseJSONLegacy(s) |
| 341 | } |
| 342 | if jsonValue != nil { |
| 343 | // Convert to map[string]any if it's an object, or handle arrays |
| 344 | if obj, ok := jsonValue.(map[string]any); ok { |
| 345 | // Skip stub objects that healed away to nothing. Partial inputs |
| 346 | // like `{`, `{"`, or `{"n` go through parseJSONWithStack and |
| 347 | // come back as `{"<marker>":1}`; after removeHealingMarkerFromJSON |
| 348 | // drops the marker key the map is empty. Returning it as a |
| 349 | // real result trips the streaming tool-call detector |
| 350 | // (chat_stream_workers.go) into thinking a tool call landed, |
| 351 | // gating off content emission for the rest of the stream |
| 352 | // (issue #9988). |
| 353 | if !(isPartialJSON && len(obj) == 0) { |
| 354 | results = append(results, obj) |
| 355 | } |
| 356 | } else if arr, ok := jsonValue.([]any); ok { |
| 357 | // Handle arrays: extract objects from array |
| 358 | for _, item := range arr { |
| 359 | if obj, ok := item.(map[string]any); ok { |
| 360 | results = append(results, obj) |
| 361 | } |
| 362 | } |
| 363 | } |
| 364 | } |
| 365 | if isPartialJSON { |
| 366 | break |
| 367 | } |
| 368 | // Skip whitespace between JSON values |
| 369 | parser.ConsumeSpaces() |
| 370 | } |
| 371 | |
| 372 | if len(results) > 0 { |
| 373 | return results, nil |
| 374 | } |
| 375 | |
| 376 | // Fallback to legacy parsing if iterative parser found nothing |
| 377 | return parseJSONLegacy(s) |
| 378 | } |
| 379 | |
| 380 | // parseJSONLegacy is the original decoder-based JSON parsing (kept for compatibility) |
| 381 | func parseJSONLegacy(s string) ([]map[string]any, error) { |
no test coverage detected