1+ using System . Diagnostics . CodeAnalysis ;
2+ using System . Text ;
3+ using STJ = System . Text . Json ;
4+ using NJ = Newtonsoft . Json ;
5+
6+ namespace QsNet . Comparison ;
7+
8+ [ ExcludeFromCodeCoverage ]
9+ internal abstract class Program
10+ {
11+ private static void Main ( )
12+ {
13+ Console . OutputEncoding = new UTF8Encoding ( false ) ;
14+
15+ var jsonPath = Path . Combine ( AppContext . BaseDirectory , "js" , "test_cases.json" ) ;
16+ if ( ! File . Exists ( jsonPath ) )
17+ {
18+ Console . Error . WriteLine ( $ "Missing test_cases.json at { jsonPath } ") ;
19+ Environment . Exit ( 1 ) ;
20+ }
21+
22+ var json = File . ReadAllText ( jsonPath ) ;
23+
24+ var cases = STJ . JsonSerializer . Deserialize < List < TestCase > > ( json , new STJ . JsonSerializerOptions
25+ {
26+ PropertyNameCaseInsensitive = true
27+ } ) ;
28+
29+ if ( cases is null )
30+ {
31+ Console . Error . WriteLine ( "No test cases loaded." ) ;
32+ Environment . Exit ( 1 ) ;
33+ }
34+
35+ var percentEncodeBrackets = true ;
36+
37+ foreach ( var c in cases )
38+ {
39+ // Convert JSON element -> CLR object graph
40+ var dataObj = c . Data . HasValue ? FromJsonElement ( c . Data . Value ) : null ;
41+
42+ // Encode (optionally percent-encode '[' and ']')
43+ var encodedOut = Qs . Encode ( dataObj ?? new Dictionary < string , object ? > ( ) ) ;
44+ if ( percentEncodeBrackets )
45+ encodedOut = encodedOut . Replace ( "[" , "%5B" ) . Replace ( "]" , "%5D" ) ;
46+ Console . WriteLine ( $ "Encoded: { encodedOut } ") ;
47+
48+ // Decode and JSON-serialize with Newtonsoft (keeps emojis as real characters)
49+ var decodedOut = Qs . Decode ( c . Encoded ! ) ;
50+ Console . WriteLine ( $ "Decoded: { CanonJson ( decodedOut ) } ") ;
51+ }
52+ }
53+
54+ // Use Newtonsoft so emojis aren’t turned into surrogate-pair escapes
55+ private static string CanonJson ( object ? v )
56+ {
57+ return NJ . JsonConvert . SerializeObject (
58+ v ,
59+ NJ . Formatting . None ,
60+ new NJ . JsonSerializerSettings
61+ {
62+ // Default doesn't escape non-ASCII; keep it explicit in case of future changes
63+ StringEscapeHandling = NJ . StringEscapeHandling . Default
64+ }
65+ ) ;
66+ }
67+
68+ private static object ? FromJsonElement ( STJ . JsonElement e )
69+ {
70+ switch ( e . ValueKind )
71+ {
72+ case STJ . JsonValueKind . Null :
73+ case STJ . JsonValueKind . Undefined :
74+ return null ;
75+
76+ case STJ . JsonValueKind . String :
77+ return e . GetString ( ) ;
78+
79+ case STJ . JsonValueKind . Number :
80+ // keep numbers as strings to match qs parse/stringify behavior
81+ return e . GetRawText ( ) . Trim ( '"' ) ;
82+
83+ case STJ . JsonValueKind . True :
84+ case STJ . JsonValueKind . False :
85+ return e . GetBoolean ( ) ;
86+
87+ case STJ . JsonValueKind . Array :
88+ {
89+ var list = new List < object ? > ( ) ;
90+ foreach ( var item in e . EnumerateArray ( ) )
91+ list . Add ( FromJsonElement ( item ) ) ;
92+ return list ;
93+ }
94+
95+ case STJ . JsonValueKind . Object :
96+ {
97+ var dict = new Dictionary < string , object ? > ( ) ;
98+ foreach ( var prop in e . EnumerateObject ( ) )
99+ dict [ prop . Name ] = FromJsonElement ( prop . Value ) ;
100+ return dict ;
101+ }
102+
103+ default :
104+ return e . GetRawText ( ) ;
105+ }
106+ }
107+
108+ private class TestCase
109+ {
110+ public string ? Encoded { get ; set ; }
111+ public STJ . JsonElement ? Data { get ; set ; }
112+ }
113+ }
0 commit comments