@@ -451,9 +451,13 @@ fn extract_array_from_string(s: &str) -> Option<Value> {
451451 }
452452
453453 // Try to find matching closing bracket by parsing incrementally
454- // Start from the opening bracket and try to parse increasingly longer
455- // substrings We'll try the json_repair on the extracted portion
456- for end_idx in ( start_idx + 1 ..=s. len ( ) ) . rev ( ) {
454+ // Start from the opening bracket and try increasingly shorter substrings.
455+ // We iterate over valid char boundaries to avoid panicking on multi-byte
456+ // UTF-8 characters where byte offsets can land inside a character.
457+ for ( end_idx, _) in s. char_indices ( ) . rev ( ) {
458+ if end_idx <= start_idx {
459+ break ;
460+ }
457461 let candidate = & s[ start_idx..end_idx] ;
458462
459463 // Try to repair and parse this candidate
@@ -464,6 +468,15 @@ fn extract_array_from_string(s: &str) -> Option<Value> {
464468 }
465469 }
466470
471+ // Also try the full string as a last resort (end at s.len() which is
472+ // always a valid boundary)
473+ let candidate = & s[ start_idx..] ;
474+ if let Ok ( parsed) = crate :: json_repair :: < Value > ( candidate)
475+ && parsed. is_array ( )
476+ {
477+ return Some ( parsed) ;
478+ }
479+
467480 None
468481}
469482
@@ -1344,4 +1357,39 @@ mod tests {
13441357 let expected = json ! ( { "count" : null} ) ;
13451358 assert_eq ! ( actual, expected) ;
13461359 }
1360+
1361+ #[ test]
1362+ fn test_extract_array_from_string_with_multibyte_chars ( ) {
1363+ // Multi-byte UTF-8 characters (like arrows and emojis) should not
1364+ // cause panics when extract_array_from_string iterates over byte
1365+ // positions. The function must only slice at valid char boundaries.
1366+ let input = "prefix → [1, 2, 3] suffix" ;
1367+ let result = extract_array_from_string ( input) ;
1368+ assert ! ( result. is_some( ) ) ;
1369+ let arr = result. unwrap ( ) ;
1370+ assert ! ( arr. is_array( ) ) ;
1371+ assert_eq ! ( arr. as_array( ) . unwrap( ) . len( ) , 3 ) ;
1372+ }
1373+
1374+ #[ test]
1375+ fn test_extract_array_from_string_with_emoji_prefix ( ) {
1376+ // Emoji characters are 4 bytes each, many byte positions inside them
1377+ // are invalid char boundaries.
1378+ let input = "🔑🔒 [4, 5, 6]" ;
1379+ let result = extract_array_from_string ( input) ;
1380+ assert ! ( result. is_some( ) ) ;
1381+ let arr = result. unwrap ( ) ;
1382+ assert ! ( arr. is_array( ) ) ;
1383+ assert_eq ! ( arr. as_array( ) . unwrap( ) . len( ) , 3 ) ;
1384+ }
1385+
1386+ #[ test]
1387+ fn test_extract_array_from_string_with_multibyte_inside_array ( ) {
1388+ // Multi-byte chars inside the array value itself
1389+ let input = r#"["αβγ", "δεζ"]"# ;
1390+ let result = extract_array_from_string ( input) ;
1391+ assert ! ( result. is_some( ) ) ;
1392+ let arr = result. unwrap ( ) ;
1393+ assert ! ( arr. is_array( ) ) ;
1394+ }
13471395}
0 commit comments