@@ -16,40 +16,20 @@ public static class SettingsJsonSanitizer
1616 /// </summary>
1717 public static byte [ ] SanitizeBytes ( byte [ ] rawBytes )
1818 {
19- // Check if any null bytes exist first (fast path for clean files)
20- var hasNullBytes = false ;
21- foreach ( var b in rawBytes )
22- {
23- if ( b == 0x00 )
24- {
25- hasNullBytes = true ;
26- break ;
27- }
28- }
29-
30- if ( ! hasNullBytes )
19+ // Fast path for clean files
20+ if ( Array . IndexOf ( rawBytes , ( byte ) 0x00 ) < 0 )
3121 return rawBytes ;
3222
33- // Filter out null bytes
34- var result = new byte [ rawBytes . Length ] ;
35- var writeIndex = 0 ;
36- foreach ( var b in rawBytes )
37- {
38- if ( b != 0x00 )
39- {
40- result [ writeIndex ++ ] = b ;
41- }
42- }
43-
44- return result . AsSpan ( 0 , writeIndex ) . ToArray ( ) ;
23+ return Array . FindAll ( rawBytes , b => b != 0x00 ) ;
4524 }
4625
4726 /// <summary>
48- /// Ensures the JSON text has matching curly braces by appending missing closing braces.
27+ /// Ensures the JSON text has matching brackets by appending missing closing braces/brackets.
28+ /// Uses a stack to correctly handle nested and mixed <c>{}</c> / <c>[]</c> pairs.
4929 /// </summary>
5030 public static string TryFixBraces ( string jsonText )
5131 {
52- var openCount = 0 ;
32+ var stack = new Stack < char > ( ) ;
5333 var inString = false ;
5434 var escaped = false ;
5535
@@ -79,73 +59,73 @@ public static string TryFixBraces(string jsonText)
7959 switch ( c )
8060 {
8161 case '{' :
82- openCount ++ ;
62+ stack . Push ( '}' ) ;
8363 break ;
84- case '}' :
85- openCount -- ;
64+ case '[' :
65+ stack . Push ( ']' ) ;
66+ break ;
67+ case '}' or ']' when stack . Count > 0 :
68+ stack . Pop ( ) ;
8669 break ;
8770 }
8871 }
8972
90- if ( openCount > 0 )
91- {
92- // Trim trailing garbage after the last valid content
93- var trimmed = jsonText . TrimEnd ( ) ;
73+ if ( stack . Count == 0 )
74+ return jsonText ;
9475
95- // If we end in the middle of a value (e.g. truncated number or string),
96- // try to find the last complete property by removing the trailing partial content
97- if ( trimmed . Length > 0 )
98- {
99- var lastChar = trimmed [ ^ 1 ] ;
100- // If the last char isn't a valid JSON value terminator, trim back to the last comma or brace
101- if ( lastChar != '"' && lastChar != '}' && lastChar != ']'
102- && ! char . IsDigit ( lastChar ) && lastChar != 'e' && lastChar != 'l'
103- && lastChar != 's' && lastChar != ',' )
104- {
105- // Find the last comma, closing bracket, or opening brace
106- var lastSafe = trimmed . LastIndexOfAny ( [ ',' , '}' , ']' , '{' ] ) ;
107- if ( lastSafe > 0 )
108- {
109- trimmed = trimmed [ ..( lastSafe + 1 ) ] ;
110- }
111- }
76+ // Trim trailing garbage after the last valid content
77+ var trimmed = jsonText . TrimEnd ( ) ;
11278
113- // Remove trailing comma before we add closing braces
114- trimmed = trimmed . TrimEnd ( ) ;
115- if ( trimmed . EndsWith ( ',' ) )
79+ // If we end in the middle of a value (e.g. truncated number or string),
80+ // trim back to the last structural character
81+ if ( trimmed . Length > 0 )
82+ {
83+ var lastChar = trimmed [ ^ 1 ] ;
84+ if ( lastChar != '"' && lastChar != '}' && lastChar != ']'
85+ && ! char . IsDigit ( lastChar ) && lastChar != 'e' && lastChar != 'l'
86+ && lastChar != 's' && lastChar != ',' )
87+ {
88+ var lastSafe = trimmed . LastIndexOfAny ( [ ',' , '}' , ']' , '{' , '[' ] ) ;
89+ if ( lastSafe > 0 )
11690 {
117- trimmed = trimmed [ ..^ 1 ] ;
91+ trimmed = trimmed [ ..( lastSafe + 1 ) ] ;
11892 }
11993 }
12094
121- // Re-count braces after trimming
122- openCount = 0 ;
123- inString = false ;
124- escaped = false ;
125- foreach ( var c in trimmed )
95+ // Remove trailing comma before we add closing brackets
96+ trimmed = trimmed . TrimEnd ( ) ;
97+ if ( trimmed . EndsWith ( ',' ) )
12698 {
127- if ( escaped ) { escaped = false ; continue ; }
128- if ( c == '\\ ' && inString ) { escaped = true ; continue ; }
129- if ( c == '"' ) { inString = ! inString ; continue ; }
130- if ( inString ) continue ;
131- switch ( c )
132- {
133- case '{' : openCount ++ ; break ;
134- case '}' : openCount -- ; break ;
135- }
99+ trimmed = trimmed [ ..^ 1 ] ;
136100 }
101+ }
137102
138- // Append missing closing braces
139- var sb = new StringBuilder ( trimmed ) ;
140- for ( var i = 0 ; i < openCount ; i ++ )
103+ // Re-scan trimmed text to rebuild the stack
104+ stack . Clear ( ) ;
105+ inString = false ;
106+ escaped = false ;
107+ foreach ( var c in trimmed )
108+ {
109+ if ( escaped ) { escaped = false ; continue ; }
110+ if ( c == '\\ ' && inString ) { escaped = true ; continue ; }
111+ if ( c == '"' ) { inString = ! inString ; continue ; }
112+ if ( inString ) continue ;
113+ switch ( c )
141114 {
142- sb . Append ( '\n ' ) . Append ( '}' ) ;
115+ case '{' : stack . Push ( '}' ) ; break ;
116+ case '[' : stack . Push ( ']' ) ; break ;
117+ case '}' or ']' when stack . Count > 0 : stack . Pop ( ) ; break ;
143118 }
119+ }
144120
145- return sb . ToString ( ) ;
121+ // Append missing closing brackets in correct LIFO order
122+ var sb = new StringBuilder ( trimmed ) ;
123+ while ( stack . Count > 0 )
124+ {
125+ sb . Append ( '\n ' ) . Append ( stack . Pop ( ) ) ;
146126 }
147127
148- return jsonText ;
128+ return sb . ToString ( ) ;
149129 }
150130
151131 /// <summary>
0 commit comments