1use serde_json::Value;
2use std::fmt::Debug;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum FuzzyJsonError {
7 #[error("Invalid JSON at position {pos}: {msg}")]
8 ParseError { pos: usize, msg: String },
9 #[error("Repair failed: {0}")]
10 RepairFailed(String),
11 #[error("JSON error: {0}")]
12 JsonError(#[from] serde_json::Error),
13}
14
15#[derive(Debug, Clone, PartialEq)]
16pub enum JsonContext {
17 Root,
18 Object,
19 Array,
20 DoubleQuoteProperty,
21 SingleQuoteProperty,
22 DoubleQuoteValue,
23 SingleQuoteValue,
24 Colon,
25}
26
27impl JsonContext {
28 pub fn is_value(&self) -> bool {
29 self == &Self::DoubleQuoteValue || self == &Self::SingleQuoteValue
30 }
31
32 pub fn is_key(&self) -> bool {
33 self == &Self::DoubleQuoteProperty || self == &Self::SingleQuoteProperty
34 }
35}
36
37#[derive(Debug, Clone)]
38pub struct ParseState {
39 pub input: String,
40 pub position: usize,
41 pub stack: Vec<JsonContext>,
42 pub output: String,
43}
44
45impl ParseState {
46 pub fn new(input: String) -> Self {
47 Self {
48 input,
49 position: 0,
50 stack: vec![JsonContext::Root],
51 output: String::new(),
52 }
53 }
54
55 pub fn current_char(&self) -> Option<char> {
56 self.input.chars().nth(self.position)
57 }
58
59 pub fn peek_chars(&self, count: usize) -> String {
60 self.input.chars().skip(self.position).take(count).collect()
61 }
62
63 pub fn advance(&mut self, count: usize) -> String {
64 let chars: String = self.input.chars().skip(self.position).take(count).collect();
65 self.position += count;
66 chars
67 }
68
69 pub fn remaining(&self) -> &str {
70 match self
71 .input
72 .char_indices()
73 .nth(self.position)
74 .map(|(idx, _)| idx)
75 {
76 Some(start_byte) => &self.input[start_byte..],
77 None => "",
78 }
79 }
80
81 pub fn is_sq_key_or_value(&self) -> bool {
82 let cc = self.current_context();
83
84 cc == &JsonContext::SingleQuoteValue || cc == &JsonContext::SingleQuoteProperty
85 }
86 pub fn is_key_or_value(&self) -> bool {
87 let cc = self.current_context();
88
89 cc == &JsonContext::SingleQuoteValue
90 || cc == &JsonContext::DoubleQuoteValue
91 || cc == &JsonContext::SingleQuoteProperty
92 || cc == &JsonContext::DoubleQuoteProperty
93 }
94
95 pub fn is_dq_key_or_value(&self) -> bool {
96 let cc = self.current_context();
97
98 cc == &JsonContext::DoubleQuoteValue || cc == &JsonContext::DoubleQuoteProperty
99 }
100
101 pub fn is_value(&self) -> bool {
102 let cc = self.current_context();
103 cc == &JsonContext::SingleQuoteValue || cc == &JsonContext::DoubleQuoteValue
104 }
105
106 pub fn is_prop(&self) -> bool {
107 let cc = self.current_context();
108 cc == &JsonContext::SingleQuoteProperty || cc == &JsonContext::DoubleQuoteProperty
109 }
110
111 pub fn is_finished(&self) -> bool {
112 self.position >= self.input.chars().count()
113 }
114
115 pub fn current_context(&self) -> &JsonContext {
116 self.stack.last().unwrap_or(&JsonContext::Root)
117 }
118
119 pub fn push_context(&mut self, context: JsonContext) {
120 self.stack.push(context);
121 }
122
123 pub fn pop_context(&mut self) -> Option<JsonContext> {
124 if self.stack.len() > 1 {
125 self.stack.pop()
126 } else {
127 None
128 }
129 }
130}
131
132pub trait RepairStrategy: Send + Sync + Debug {
133 fn name(&self) -> &'static str;
134 fn can_repair(&self, state: &ParseState, error: &str) -> bool;
135 fn repair(&self, state: &mut ParseState, error: &str) -> Result<(), FuzzyJsonError>;
136 fn priority(&self) -> u8; }
138
139pub trait StateHandler: Send + Sync + Debug {
140 fn can_handle(&self, state: &ParseState) -> bool;
141 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError>; }
143
144#[derive(Default, Debug)]
145pub struct FuzzyJsonParser {
146 repair_strategies: Vec<Box<dyn RepairStrategy>>,
147 state_handlers: Vec<Box<dyn StateHandler>>,
148 options: ParserOptions,
149}
150
151#[derive(Debug, Clone)]
152pub struct ParserOptions {
153 pub auto_repair: bool,
154 pub allow_trailing_commas: bool,
155 pub allow_comments: bool,
156 pub allow_single_quotes: bool,
157 pub allow_unquoted_keys: bool,
158 pub max_repair_attempts: usize,
159 pub strict_mode: bool,
160 pub aggressive_truncation_repair: bool, }
162
163impl Default for ParserOptions {
164 fn default() -> Self {
165 Self {
166 auto_repair: true,
167 allow_trailing_commas: true,
168 allow_comments: true,
169 allow_single_quotes: true,
170 allow_unquoted_keys: false,
171 max_repair_attempts: 1500,
172 strict_mode: false,
173 aggressive_truncation_repair: true, }
175 }
176}
177
178impl FuzzyJsonParser {
179 pub fn new() -> Self {
180 let mut parser = Self::default();
181 parser.register_default_strategies();
182 parser.register_default_handlers();
183 parser
184 }
185
186 pub fn aggressively_close_scopes(&self, json_str: &str) -> Result<String, FuzzyJsonError> {
207 if !self.options.aggressive_truncation_repair {
208 return Ok(json_str.to_string());
209 }
210 let mut state = ParseState::new(json_str.trim().to_string());
211 let mut in_string = false;
212 let mut string_quote_char = '"';
213 let mut escape_next = false;
214
215 let mut scope_stack: Vec<(JsonContext, usize)> = vec![(JsonContext::Root, 0)];
217
218 while !state.is_finished() {
219 let ch = match state.current_char() {
220 Some(c) => c,
221 None => break,
222 };
223
224 if escape_next {
225 state.output.push(ch);
226 state.advance(1);
227 escape_next = false;
228 continue;
229 }
230
231 match ch {
232 '\\' if in_string => {
233 state.output.push(ch);
234 state.advance(1);
235 escape_next = true;
236 }
237 '"' | '\'' if !in_string => {
238 in_string = true;
239 string_quote_char = ch;
240 state.output.push(ch);
241 state.advance(1);
242 }
243 c if in_string && c == string_quote_char => {
244 in_string = false;
245 state.output.push(ch);
246 state.advance(1);
247 }
248 '{' if !in_string => {
249 scope_stack.push((JsonContext::Object, state.position));
250 state.output.push(ch);
251 state.advance(1);
252 }
253 '[' if !in_string => {
254 scope_stack.push((JsonContext::Array, state.position));
255 state.output.push(ch);
256 state.advance(1);
257 }
258 '}' if !in_string => {
259 if let Some((JsonContext::Object, _)) = scope_stack.last() {
260 scope_stack.pop();
261 }
262 state.output.push(ch);
263 state.advance(1);
264 }
265 ']' if !in_string => {
266 if let Some((JsonContext::Array, _)) = scope_stack.last() {
267 scope_stack.pop();
268 }
269 state.output.push(ch);
270 state.advance(1);
271 }
272 _ => {
273 state.output.push(ch);
274 state.advance(1);
275 }
276 }
277 }
278
279 self.close_remaining_scopes(&mut state, in_string, string_quote_char, scope_stack)?;
281
282 Ok(state.output)
283 }
284
285 fn close_remaining_scopes(
286 &self,
287 state: &mut ParseState,
288 in_string: bool,
289 string_quote_char: char,
290 mut scope_stack: Vec<(JsonContext, usize)>,
291 ) -> Result<(), FuzzyJsonError> {
292 if in_string {
294 state.output.push(string_quote_char);
295 }
297
298 let trimmed_output = state.output.trim_end();
300 if trimmed_output.ends_with(',') {
301 state.output = trimmed_output[..trimmed_output.len() - 1].to_string();
302 }
303
304 while let Some((context, _pos)) = scope_stack.pop() {
306 match context {
307 JsonContext::Object => {
308 state.output.push('}');
309 }
310 JsonContext::Array => {
311 state.output.push(']');
312 }
313 JsonContext::Root => {
314 break;
316 }
317 _ => {} }
319 }
320
321 Ok(())
322 }
323
324 pub fn with_options(options: ParserOptions) -> Self {
325 let mut parser = Self {
326 options,
327 ..Default::default()
328 };
329 parser.register_default_strategies();
330 parser.register_default_handlers();
331 parser
332 }
333
334 pub fn register_strategy(&mut self, strategy: Box<dyn RepairStrategy>) {
335 self.repair_strategies.push(strategy);
336 self.repair_strategies
338 .sort_by(|a, b| b.priority().cmp(&a.priority()));
339 }
340
341 pub fn register_handler(&mut self, handler: Box<dyn StateHandler>) {
342 self.state_handlers.push(handler);
343 }
344
345 pub fn parse<T>(&self, json_str: &str) -> Result<T, FuzzyJsonError>
346 where
347 T: serde::de::DeserializeOwned,
348 {
349 let value = self.parse_value(json_str)?;
350 serde_json::from_value(value).map_err(FuzzyJsonError::JsonError)
351 }
352
353 pub fn parse_value(&self, json_str: &str) -> Result<Value, FuzzyJsonError> {
354 match serde_json::from_str(json_str) {
356 Ok(value) => Ok(value),
357 Err(e) => {
358 if !self.options.auto_repair {
359 return Err(FuzzyJsonError::RepairFailed(
360 "Auto-repair disabled".to_string(),
361 ));
362 }
363
364 let repaired = self.repair_json(json_str, e)?;
366 serde_json::from_str(&repaired).map_err(FuzzyJsonError::JsonError)
367 }
368 }
369 }
370
371 pub fn repair_json(
372 &self,
373 json_str: &str,
374 e: serde_json::error::Error,
375 ) -> Result<String, FuzzyJsonError> {
376 let mut state = ParseState::new(json_str.trim().to_string());
377 let mut attempts = 0;
378
379 self.try_repair_strategies(&mut state, &e.to_string())?;
380 while !state.is_finished() && attempts < self.options.max_repair_attempts {
389 let mut handled = false;
390
391 for handler in &self.state_handlers {
393 if handler.can_handle(&state) {
394 match handler.handle(&mut state) {
406 Ok(should_continue) => {
407 handled = true;
408 if !should_continue {
409 return Ok(state.output);
410 }
411 break;
412 }
413 Err(e) => {
414 if self.try_repair_strategies(&mut state, &e.to_string())? {
417 handled = true;
418 break;
419 }
420 }
421 }
422 } else {
423 }
433 }
434 if !handled {
435 if self.try_repair_strategies(&mut state, &e.to_string())? {
442 handled = true;
443 }
444 }
445
446 if !handled {
447 return Err(FuzzyJsonError::ParseError {
448 pos: state.position,
449 msg: format!(
450 "No handler for current state: {:?} | {:?}",
451 state.current_context(),
452 state.current_char()
453 ),
454 });
455 }
456
457 attempts += 1;
458 }
459 if state.current_context() != &JsonContext::Root {
467 self.try_repair_strategies(&mut state, &e.to_string())?;
475 }
476
477 if attempts >= self.options.max_repair_attempts {
478 return Err(FuzzyJsonError::RepairFailed(
479 "Too many repair attempts".to_string(),
480 ));
481 }
482
483 Ok(state.output)
486 }
487
488 fn try_repair_strategies(
489 &self,
490 state: &mut ParseState,
491 error: &str,
492 ) -> Result<bool, FuzzyJsonError> {
493 for strategy in &self.repair_strategies {
495 if strategy.can_repair(state, error) {
496 strategy.repair(state, error)?;
499 return Ok(true);
500 }
501 }
502 Ok(false)
503 }
504
505 fn register_default_strategies(&mut self) {
506 self.register_strategy(Box::new(TruncationRepairStrategy));
507 self.register_strategy(Box::new(SingleQuotesStrategy));
508 self.register_strategy(Box::new(CodeBlockMarkersStrategy));
509 self.register_strategy(Box::new(IncompletePropertyStrategy));
510 self.register_strategy(Box::new(IncompleteArrayStrategy));
511 self.register_strategy(Box::new(TrailingCommaStrategy));
512 self.register_strategy(Box::new(MissingQuotesStrategy));
513 self.register_strategy(Box::new(MissingBracketsStrategy));
514 self.register_strategy(Box::new(TrimStrayContentInBeginningStrategy));
515 self.register_strategy(Box::new(TrimStrayContentInEndStrategy));
516 }
517
518 fn register_default_handlers(&mut self) {
519 self.register_handler(Box::new(WhitespaceHandler));
520 self.register_handler(Box::new(LiteralHandler));
521 self.register_handler(Box::new(ColonHandler));
522 self.register_handler(Box::new(CommaHandler));
523 self.register_handler(Box::new(StringHandler));
524 self.register_handler(Box::new(NumberHandler));
525 self.register_handler(Box::new(ObjectHandler));
526 self.register_handler(Box::new(ArrayHandler));
527 self.register_handler(Box::new(NoQuotesKeyHandler));
528 }
529}
530
531#[derive(Debug)]
533pub struct TrailingCommaStrategy;
534
535impl RepairStrategy for TrailingCommaStrategy {
536 fn name(&self) -> &'static str {
537 "trailing_comma"
538 }
539 fn priority(&self) -> u8 {
540 80
541 }
542
543 fn can_repair(&self, state: &ParseState, _error: &str) -> bool {
544 if let Some(ch) = state.current_char() {
545 ch == ','
546 && state
547 .peek_chars(2)
548 .chars()
549 .nth(1)
550 .map_or(false, |next| next == '}' || next == ']')
551 } else {
552 false
553 }
554 }
555
556 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
557 state.advance(1);
559 Ok(())
560 }
561}
562
563#[derive(Debug)]
564pub struct MissingQuotesStrategy;
565
566impl RepairStrategy for MissingQuotesStrategy {
568 fn name(&self) -> &'static str {
569 "missing_quotes"
570 }
571 fn priority(&self) -> u8 {
572 70
573 }
574
575 fn can_repair(&self, state: &ParseState, error: &str) -> bool {
576 error.contains("expected") && error.contains("quote")
577 || (state.current_context() == &JsonContext::DoubleQuoteProperty
578 && state.current_char().map_or(false, |c| c.is_alphabetic()))
579 }
580
581 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
582 println!("Repairing missing quotes");
583 state.output.push(
584 if state.current_context() == &JsonContext::SingleQuoteProperty {
585 '\''
586 } else {
587 '"'
588 },
589 );
590
591 while let Some(ch) = state.current_char() {
593 if ch.is_whitespace() || ch == ':' || ch == ',' || ch == '}' || ch == ']' {
594 break;
595 }
596 state.output.push(ch);
597 state.advance(1);
598 }
599
600 state.output.push(
601 if state.current_context() == &JsonContext::SingleQuoteProperty {
602 '\''
603 } else {
604 '"'
605 },
606 );
607 Ok(())
608 }
609}
610
611#[derive(Debug)]
612pub struct MissingBracketsStrategy;
613
614impl RepairStrategy for MissingBracketsStrategy {
615 fn name(&self) -> &'static str {
616 "missing_brackets"
617 }
618 fn priority(&self) -> u8 {
619 60
620 }
621
622 fn can_repair(&self, _state: &ParseState, error: &str) -> bool {
623 error.contains("missing") && (error.contains("}") || error.contains("]"))
624 }
625
626 fn repair(&self, state: &mut ParseState, error: &str) -> Result<(), FuzzyJsonError> {
627 if error.contains("}") {
628 state.output.push('}');
629 state.pop_context();
630 } else if error.contains("]") {
631 state.output.push(']');
632 state.pop_context();
633 }
634 Ok(())
635 }
636}
637
638#[derive(Debug)]
639pub struct CodeBlockMarkersStrategy;
640
641impl RepairStrategy for CodeBlockMarkersStrategy {
642 fn name(&self) -> &'static str {
643 "code_block_markers"
644 }
645 fn priority(&self) -> u8 {
646 90
647 }
648
649 fn can_repair(&self, state: &ParseState, _error: &str) -> bool {
650 state.remaining().starts_with("```") || state.remaining().starts_with("json```")
651 }
652
653 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
654 if state.remaining().starts_with("json```") {
655 state.advance(7);
656 } else if state.remaining().starts_with("```json") {
657 state.advance(7);
658 } else if state.remaining().starts_with("```") {
659 state.advance(3);
660 }
661 Ok(())
662 }
663}
664
665#[derive(Debug)]
666pub struct TrimStrayContentInBeginningStrategy;
667
668impl RepairStrategy for TrimStrayContentInBeginningStrategy {
669 fn name(&self) -> &'static str {
670 "trim_stray_characters_in_end_markers"
671 }
672 fn priority(&self) -> u8 {
673 70
674 }
675
676 fn can_repair(&self, state: &ParseState, _error: &str) -> bool {
677 state.current_context() == &JsonContext::Root
678 && (state.current_char() != Some('{') || state.current_char() != Some('['))
679 }
680
681 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
682 while state.current_char().is_some()
683 && state.current_char() != Some('{')
684 && state.current_char() != Some('[')
685 {
686 state.advance(1);
690 }
691 Ok(())
692 }
693}
694
695#[derive(Debug)]
696pub struct TrimStrayContentInEndStrategy;
697
698impl RepairStrategy for TrimStrayContentInEndStrategy {
699 fn name(&self) -> &'static str {
700 "trim_stray_characters_in_end_markers"
701 }
702 fn priority(&self) -> u8 {
703 70
704 }
705
706 fn can_repair(&self, state: &ParseState, _error: &str) -> bool {
707 state.current_context() == &JsonContext::Root
708 }
710
711 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
712 println!("Char: {:?}", state.current_char());
713 while state.current_char() != None {
714 state.advance(1);
715 println!("Char: {:?}", state.current_char());
716 }
717 Ok(())
718 }
719}
720
721#[derive(Debug)]
722pub struct SingleQuotesStrategy;
723
724impl RepairStrategy for SingleQuotesStrategy {
725 fn name(&self) -> &'static str {
726 "single_quotes"
727 }
728 fn priority(&self) -> u8 {
729 85 }
731
732 fn can_repair(&self, state: &ParseState, _error: &str) -> bool {
733 state.current_char() == Some('\'')
734 }
735
736 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
737 state.output.push('"');
738 state.advance(1); while let Some(ch) = state.current_char() {
741 if ch == '\'' {
742 state.advance(1);
743 break;
744 }
745 if ch == '"' {
746 state.output.push('\\');
747 }
748 state.output.push(ch);
749 state.advance(1);
750 }
751 if state.current_context() == &JsonContext::Colon {
752 state.pop_context(); }
758
759 state.output.push('"');
760 Ok(())
761 }
762}
763
764#[derive(Debug)]
766pub struct TruncationRepairStrategy;
767
768impl RepairStrategy for TruncationRepairStrategy {
769 fn name(&self) -> &'static str {
770 "truncation_repair"
771 }
772 fn priority(&self) -> u8 {
773 95
774 } fn can_repair(&self, state: &ParseState, error: &str) -> bool {
777 state.is_finished()
779 || error.contains("unexpected end")
780 || error.contains("unclosed")
781 || (state.remaining().trim().is_empty() && !state.stack.is_empty())
782 }
783
784 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
785 self.close_all_scopes(state)
787 }
788}
789
790impl TruncationRepairStrategy {
791 fn close_all_scopes(&self, state: &mut ParseState) -> Result<(), FuzzyJsonError> {
792 let mut needs_closing = Vec::new();
794
795 for context in state.stack.iter().rev() {
797 match context {
798 JsonContext::Object => needs_closing.push('}'),
799 JsonContext::Array => needs_closing.push(']'),
800 JsonContext::DoubleQuoteProperty |JsonContext::SingleQuoteProperty => {
801 if state.output.chars().last() != Some('"')
804 && state.output.matches('"').count() % 2 != 0
805 {
806 println!("maybe the root cause @ 805");
807 needs_closing.push('"'); }
809 needs_closing.push(':'); needs_closing.push('0'); }
814 JsonContext::Colon => {
815 needs_closing.push('0'); }
817 JsonContext::DoubleQuoteValue => {
818 if state.output.chars().last() == Some('"')
820 && state.output.matches('"').count() % 2 != 0
821 {
822 needs_closing.push('"'); }
824 }
825 _ => {} }
827 }
828
829 if self.is_in_unclosed_string(&state.output) {
831 needs_closing.insert(0, '"');
833 }
834
835 if state.output.trim_end().ends_with(',') {
837 let trimmed = state.output.trim_end();
838 state.output = trimmed[..trimmed.len() - 1].to_string();
839 }
840
841 for &closing_char in &needs_closing {
843 state.output.push(closing_char);
844 }
845
846 Ok(())
847 }
848
849 fn is_in_unclosed_string(&self, output: &str) -> bool {
850 let mut in_string = false;
851 let mut escape_next = false;
852 let mut quote_char = '"';
853
854 for ch in output.chars() {
855 if escape_next {
856 escape_next = false;
857 continue;
858 }
859
860 match ch {
861 '\\' if in_string => escape_next = true,
862 '"' | '\'' if !in_string => {
863 in_string = true;
864 quote_char = ch;
865 }
866 c if in_string && c == quote_char => in_string = false,
867 _ => {}
868 }
869 }
870
871 in_string
872 }
873}
874
875#[derive(Debug)]
877pub struct IncompletePropertyStrategy;
878
879impl RepairStrategy for IncompletePropertyStrategy {
880 fn name(&self) -> &'static str {
881 "incomplete_property"
882 }
883 fn priority(&self) -> u8 {
884 85
885 }
886
887 fn can_repair(&self, state: &ParseState, _error: &str) -> bool {
888 let output = state.output.trim_end();
889 output.ends_with(':')
891 || (output.ends_with('"') && state.remaining().trim().starts_with(':'))
892 }
893
894 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
895 let output = state.output.trim_end();
896
897 if output.ends_with(':') {
898 state.output.push_str(" null");
900 } else if output.ends_with('"') && state.remaining().trim().starts_with(':') {
901 state.output.push_str(": null");
903 while let Some(ch) = state.current_char() {
905 if ch == ':' {
906 state.advance(1);
907 break;
908 }
909 if !ch.is_whitespace() {
910 break;
911 }
912 state.advance(1);
913 }
914 }
915
916 Ok(())
917 }
918}
919
920#[derive(Debug)]
922pub struct IncompleteArrayStrategy;
923
924impl RepairStrategy for IncompleteArrayStrategy {
925 fn name(&self) -> &'static str {
926 "incomplete_array"
927 }
928 fn priority(&self) -> u8 {
929 80
930 }
931
932 fn can_repair(&self, state: &ParseState, _error: &str) -> bool {
933 state.current_context() == &JsonContext::Array
934 && state.output.trim_end().ends_with(',')
935 && state.remaining().trim().is_empty()
936 }
937
938 fn repair(&self, state: &mut ParseState, _error: &str) -> Result<(), FuzzyJsonError> {
939 let trimmed = state.output.trim_end();
941 if trimmed.ends_with(',') {
942 state.output = trimmed[..trimmed.len() - 1].to_string();
943 }
944 state.output.push(']');
945 Ok(())
946 }
947}
948
949#[derive(Debug)]
951pub struct WhitespaceHandler;
952
953impl StateHandler for WhitespaceHandler {
954 fn can_handle(&self, state: &ParseState) -> bool {
955 state.current_char().map_or(false, |c| c.is_whitespace())
956 || state.remaining().starts_with("\\n")
957 }
958
959 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
960 while state.current_char().map_or(false, |a| a.is_whitespace())
961 || state.remaining().starts_with("\\n")
962 {
963 if state.remaining().starts_with("\\n") {
965 state.advance(2);
966 } else {
967 state.advance(1);
968 }
969 }
970 Ok(true)
971 }
972}
973#[derive(Debug)]
974pub struct CommaHandler;
975
976impl StateHandler for CommaHandler {
977 fn can_handle(&self, state: &ParseState) -> bool {
978 let remaining = state.remaining();
979 remaining.starts_with(",")
986 }
987
988 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
989 let remaining = state.remaining();
990
991 if remaining.starts_with(",") {
992 state.advance(1);
993
994 let mut remaining = state.remaining();
995
996 while remaining.starts_with("\\n")
997 || state.current_char().map(|a| a.is_whitespace()) == Some(true)
998 {
999 if remaining.starts_with("\\n") {
1000 state.advance(2);
1001 } else {
1002 state.advance(1);
1003 }
1004 remaining = state.remaining();
1005 }
1006
1007 if state.current_char() == Some('}') {
1012 state.output.push('}');
1013 state.advance(1);
1014 state.pop_context();
1015 return Ok(true);
1016 }
1017 state.output.push_str(",");
1018 }
1019
1020 Ok(true)
1021 }
1022}
1023#[derive(Debug)]
1024pub struct ColonHandler;
1025
1026impl StateHandler for ColonHandler {
1027 fn can_handle(&self, state: &ParseState) -> bool {
1028 let remaining = state.remaining();
1029 remaining.starts_with(":")
1030 }
1031
1032 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
1033 if state.is_prop() {
1039 state.pop_context();
1040 state.push_context(JsonContext::Colon);
1041 }
1042
1043 let remaining = state.remaining();
1044 if remaining.starts_with(":") {
1045 state.output.push(':');
1046 state.advance(1);
1047 }
1048 while state.current_char().map_or(false, |a| a.is_whitespace())
1049 || state.remaining().starts_with("\\n")
1050 {
1051 if state.remaining().starts_with("\\n") {
1052 state.advance(2);
1053 } else {
1054 state.advance(1);
1055 }
1056 }
1057 Ok(true)
1067 }
1068}
1069
1070#[derive(Debug)]
1071pub struct LiteralHandler;
1072
1073impl StateHandler for LiteralHandler {
1074 fn can_handle(&self, state: &ParseState) -> bool {
1075 let remaining = state.remaining().trim();
1076 (state.current_context() == &JsonContext::Array
1078 || state.current_context() == &JsonContext::Colon
1079 || state.current_context().is_key())
1080 && (remaining.starts_with("true")
1081 || remaining.starts_with("false")
1082 || remaining.starts_with("null"))
1083 }
1084
1085 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
1086 let remaining = state.remaining();
1087
1088 if remaining.starts_with("true") {
1089 state.output.push_str("true");
1090 state.advance(4);
1091 } else if remaining.starts_with("false") {
1092 state.output.push_str("false");
1093 state.advance(5);
1094 } else if remaining.starts_with("null") {
1095 state.output.push_str("null");
1096 state.advance(4);
1097 }
1098 if state.current_context() != &JsonContext::Array {
1099 state.pop_context(); }
1102
1103 Ok(true)
1104 }
1105}
1106
1107const VALID_KEY_FIRST_CHARS: [char; 27] = [
1108 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
1109 't', 'u', 'v', 'w', 'x', 'y', 'z', '_',
1110];
1111const VALID_KEY_REST_OF_CHARS: [char; 10] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
1112
1113#[derive(Debug)]
1114pub struct NoQuotesKeyHandler;
1115
1116impl StateHandler for NoQuotesKeyHandler {
1117 fn can_handle(&self, state: &ParseState) -> bool {
1118 (state.current_context() == &JsonContext::Object)
1119 && (state
1120 .current_char()
1121 .map(|c| VALID_KEY_FIRST_CHARS.contains(&c.to_ascii_lowercase()))
1122 == Some(true))
1123 }
1124
1125 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
1126 state.push_context(JsonContext::DoubleQuoteProperty);
1133 state.output.push('"');
1134
1135 while let Some(ch) = state.current_char() {
1136 if VALID_KEY_FIRST_CHARS.contains(&ch.to_ascii_lowercase())
1137 || VALID_KEY_REST_OF_CHARS.contains(&ch)
1138 {
1139 state.output.push(ch);
1140 state.advance(1);
1141 } else {
1142 state.output.push('"');
1143 break;
1144 }
1145 }
1146
1147 Ok(true)
1148 }
1149}
1150
1151#[derive(Debug)]
1152pub struct StringHandler;
1153
1154impl StateHandler for StringHandler {
1155 fn can_handle(&self, state: &ParseState) -> bool {
1156 (state.is_sq_key_or_value() && state.current_char() == Some('\''))
1157 || (state.is_dq_key_or_value() && state.current_char() == Some('"'))
1158 || (!state.is_key_or_value()
1159 && (state.current_char() == Some('"') || state.current_char() == Some('\'')))
1160 }
1161
1162 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
1163 let boundary_char = state.current_char().unwrap(); state.output.push('"');
1176 state.advance(1);
1177
1178 if state.current_context() == &JsonContext::Colon {
1179 state.pop_context();
1180 state.push_context(if boundary_char == '"' {
1181 JsonContext::DoubleQuoteValue
1182 } else {
1183 JsonContext::SingleQuoteValue
1184 });
1185 } else if state.is_prop() {
1186 } else if state.current_context() == &JsonContext::Array {
1203 state.push_context(if boundary_char == '"' {
1204 JsonContext::DoubleQuoteValue
1205 } else {
1206 JsonContext::SingleQuoteValue
1207 });
1208 } else {
1209 state.push_context(if boundary_char == '"' {
1210 JsonContext::DoubleQuoteProperty
1211 } else {
1212 JsonContext::SingleQuoteProperty
1213 });
1214 }
1215
1216 while let Some(ch) = state.current_char() {
1217 if ch == boundary_char {
1218 state.output.push('"');
1219 state.advance(1);
1220 if state.is_value() {
1228 state.pop_context();
1229 }
1230 break;
1231 }
1232
1233 if ch == '\\' {
1234 state.output.push('\\');
1235 state.advance(1);
1236 if let Some(escaped) = state.current_char() {
1237 state.output.push(escaped);
1238 state.advance(1);
1239 }
1240 } else {
1241 state.output.push(ch);
1242 state.advance(1);
1243 }
1244 }
1245
1246 Ok(true)
1247 }
1248}
1249
1250#[derive(Debug)]
1251pub struct NumberHandler;
1252
1253impl StateHandler for NumberHandler {
1254 fn can_handle(&self, state: &ParseState) -> bool {
1255 state
1256 .current_char()
1257 .map_or(false, |c| c.is_ascii_digit() || c == '-')
1258 }
1259
1260 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
1261 if state.current_context() == &JsonContext::Colon {
1262 state.pop_context();
1263 state.push_context(JsonContext::DoubleQuoteValue);
1264 } else if state.current_context() == &JsonContext::DoubleQuoteProperty {
1265 state.pop_context();
1266 state.push_context(JsonContext::DoubleQuoteValue);
1267 state.output.push(':');
1268 } else if state.current_context() == &JsonContext::Array {
1269 state.push_context(JsonContext::DoubleQuoteValue);
1270 } else {
1271 state.push_context(JsonContext::DoubleQuoteProperty);
1272 state.output.push('"');
1273 }
1274
1275 while let Some(ch) = state.current_char() {
1276 if ch.is_ascii_digit() || ch == '-' || ch == '+' || ch == '.' || ch == 'e' || ch == 'E'
1277 {
1278 state.output.push(ch);
1279 state.advance(1);
1280 } else {
1281 break;
1282 }
1283 }
1284
1285 if state.current_context() == &JsonContext::DoubleQuoteValue {
1286 state.pop_context();
1287 } else if state.current_context() == &JsonContext::DoubleQuoteProperty
1288 && state
1289 .current_char()
1290 .map_or(true, |c| c.is_whitespace() || c == ':' || c == '}')
1291 {
1292 state.output.push('"');
1293 }
1294 Ok(true)
1295 }
1296}
1297
1298#[derive(Debug)]
1299pub struct ObjectHandler;
1300
1301impl StateHandler for ObjectHandler {
1302 fn can_handle(&self, state: &ParseState) -> bool {
1303 state.current_char() == Some('{')
1304 || (state.current_context() != &JsonContext::Root && state.current_char() == Some('}'))
1305 }
1306
1307 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
1308 if state.current_context() == &JsonContext::Colon {
1309 state.pop_context();
1310 }
1311 if let Some(ch) = state.current_char() {
1312 if ch == '{' {
1313 state.output.push('{');
1314 state.push_context(JsonContext::Object);
1315 state.advance(1);
1316 } else if ch == '}' {
1317 state.output.push('}');
1318 state.pop_context();
1319 state.advance(1);
1320 }
1321 }
1322 Ok(true)
1323 }
1324}
1325
1326#[derive(Debug)]
1327pub struct ArrayHandler;
1328
1329impl StateHandler for ArrayHandler {
1330 fn can_handle(&self, state: &ParseState) -> bool {
1331 state.current_char() == Some('[') || state.current_char() == Some(']')
1332 }
1333
1334 fn handle(&self, state: &mut ParseState) -> Result<bool, FuzzyJsonError> {
1335 if state.current_context() == &JsonContext::Colon {
1336 state.pop_context();
1337 }
1338 if let Some(ch) = state.current_char() {
1339 if ch == '[' {
1340 state.output.push('[');
1341 state.push_context(JsonContext::Array);
1342 state.advance(1);
1343 } else if ch == ']' {
1344 state.output.push(']');
1345 state.pop_context();
1346 state.advance(1);
1347 }
1348 }
1349 Ok(true)
1350 }
1351}
1352
1353pub struct FuzzyJsonParserBuilder {
1355 options: ParserOptions,
1356 custom_strategies: Vec<Box<dyn RepairStrategy>>,
1357 custom_handlers: Vec<Box<dyn StateHandler>>,
1358}
1359
1360impl FuzzyJsonParserBuilder {
1361 pub fn new() -> Self {
1362 Self {
1363 options: ParserOptions::default(),
1364 custom_strategies: Vec::new(),
1365 custom_handlers: Vec::new(),
1366 }
1367 }
1368
1369 pub fn with_trailing_commas(mut self, allow: bool) -> Self {
1370 self.options.allow_trailing_commas = allow;
1371 self
1372 }
1373
1374 pub fn with_single_quotes(mut self, allow: bool) -> Self {
1375 self.options.allow_single_quotes = allow;
1376 self
1377 }
1378
1379 pub fn with_comments(mut self, allow: bool) -> Self {
1380 self.options.allow_comments = allow;
1381 self
1382 }
1383
1384 pub fn with_unquoted_keys(mut self, allow: bool) -> Self {
1385 self.options.allow_unquoted_keys = allow;
1386 self
1387 }
1388
1389 pub fn strict_mode(mut self, strict: bool) -> Self {
1390 self.options.strict_mode = strict;
1391 self
1392 }
1393
1394 pub fn max_repair_attempts(mut self, max: usize) -> Self {
1395 self.options.max_repair_attempts = max;
1396 self
1397 }
1398
1399 pub fn aggressive_truncation_repair(mut self, enable: bool) -> Self {
1400 self.options.aggressive_truncation_repair = enable;
1401 self
1402 }
1403
1404 pub fn add_strategy(mut self, strategy: Box<dyn RepairStrategy>) -> Self {
1405 self.custom_strategies.push(strategy);
1406 self
1407 }
1408
1409 pub fn add_handler(mut self, handler: Box<dyn StateHandler>) -> Self {
1410 self.custom_handlers.push(handler);
1411 self
1412 }
1413
1414 pub fn build(self) -> FuzzyJsonParser {
1415 let mut parser = FuzzyJsonParser::with_options(self.options);
1416
1417 for strategy in self.custom_strategies {
1418 parser.register_strategy(strategy);
1419 }
1420
1421 for handler in self.custom_handlers {
1422 parser.register_handler(handler);
1423 }
1424
1425 parser
1426 }
1427}
1428
1429impl Default for FuzzyJsonParserBuilder {
1430 fn default() -> Self {
1431 Self::new()
1432 }
1433}