@@ -81,9 +81,9 @@ public void RoundTripTuple(JsonSerializer addSerializers, JsonSerializer expecte
81
81
82
82
[ Theory ]
83
83
[ InlineData ( JsonSerializer . None , JsonSerializer . FieldEnabled ) ]
84
- [ InlineData ( JsonSerializer . CustomGlobal , JsonSerializer . FieldEnabled ) ]
85
- [ InlineData ( JsonSerializer . CustomPerType , JsonSerializer . FieldEnabled ) ]
86
- [ InlineData ( JsonSerializer . CustomPerType | JsonSerializer . CustomGlobal , JsonSerializer . FieldEnabled ) ]
84
+ [ InlineData ( JsonSerializer . CustomGlobal , JsonSerializer . CustomGlobal ) ]
85
+ [ InlineData ( JsonSerializer . CustomPerType , JsonSerializer . CustomPerType ) ]
86
+ [ InlineData ( JsonSerializer . CustomPerType | JsonSerializer . CustomGlobal , JsonSerializer . CustomPerType ) ]
87
87
public void RoundTripValueTuple ( JsonSerializer addSerializers , JsonSerializer expectedSerializer )
88
88
{
89
89
var obj = RoundTrip ( ( 42 , "abc" ) , """{"Item1":42,"Item2":"abc"}"""u8 , expectedSerializer , addSerializers ) ;
@@ -93,16 +93,146 @@ public void RoundTripValueTuple(JsonSerializer addSerializers, JsonSerializer ex
93
93
94
94
[ Theory ]
95
95
[ InlineData ( JsonSerializer . None , JsonSerializer . FieldEnabled ) ]
96
- [ InlineData ( JsonSerializer . CustomGlobal , JsonSerializer . FieldEnabled ) ]
97
- [ InlineData ( JsonSerializer . CustomPerType , JsonSerializer . FieldEnabled ) ]
98
- [ InlineData ( JsonSerializer . CustomPerType | JsonSerializer . CustomGlobal , JsonSerializer . FieldEnabled ) ]
96
+ [ InlineData ( JsonSerializer . CustomGlobal , JsonSerializer . CustomGlobal ) ]
97
+ [ InlineData ( JsonSerializer . CustomPerType , JsonSerializer . CustomPerType ) ]
98
+ [ InlineData ( JsonSerializer . CustomPerType | JsonSerializer . CustomGlobal , JsonSerializer . CustomPerType ) ]
99
99
public void RoundTripNamedValueTuple ( JsonSerializer addSerializers , JsonSerializer expectedSerializer )
100
100
{
101
101
var obj = RoundTrip ( ( X : 42 , Y : "abc" ) , """{"Item1":42,"Item2":"abc"}"""u8 , expectedSerializer , addSerializers ) ;
102
102
Assert . Equal ( 42 , obj . X ) ;
103
103
Assert . Equal ( "abc" , obj . Y ) ;
104
104
}
105
105
106
+ [ Fact ]
107
+ public void RoundTripValueTupleList ( )
108
+ {
109
+ List < ( int , string ) > source = [ ( 1 , "a" ) , ( 2 , "b" ) ] ;
110
+ var clone = RoundTrip ( source , """[{"Item1":1,"Item2":"a"},{"Item1":2,"Item2":"b"}]"""u8 , JsonSerializer . FieldEnabled ) ;
111
+ Assert . Equal ( source , clone ) ;
112
+ }
113
+
114
+ [ Fact ]
115
+ public void RoundTripValueTupleArray ( )
116
+ {
117
+ ( int , string ) [ ] source = [ ( 1 , "a" ) , ( 2 , "b" ) ] ;
118
+ var clone = RoundTrip ( source , """[{"Item1":1,"Item2":"a"},{"Item1":2,"Item2":"b"}]"""u8 , JsonSerializer . FieldEnabled ) ;
119
+ Assert . Equal ( source , clone ) ;
120
+ }
121
+
122
+ [ Fact ]
123
+ public void RoundTripTupleList ( )
124
+ {
125
+ List < Tuple < int , string > > source = [ Tuple . Create ( 1 , "a" ) , Tuple . Create ( 2 , "b" ) ] ;
126
+ var clone = RoundTrip ( source , """[{"Item1":1,"Item2":"a"},{"Item1":2,"Item2":"b"}]"""u8 , JsonSerializer . Default ) ;
127
+ Assert . Equal ( source , clone ) ;
128
+ }
129
+
130
+ [ Fact ]
131
+ public void RoundTripTupleArray ( )
132
+ {
133
+ Tuple < int , string > [ ] source = [ Tuple . Create ( 1 , "a" ) , Tuple . Create ( 2 , "b" ) ] ;
134
+ var clone = RoundTrip ( source , """[{"Item1":1,"Item2":"a"},{"Item1":2,"Item2":"b"}]"""u8 , JsonSerializer . Default ) ;
135
+ Assert . Equal ( source , clone ) ;
136
+ }
137
+
138
+ [ Fact ]
139
+ public void RoundTripFieldOnlyPoco ( )
140
+ {
141
+ var source = new FieldOnlyPoco { X = 1 , Y = "a" } ;
142
+ var clone = RoundTrip ( source , """{"X":1,"Y":"a"}"""u8 , JsonSerializer . FieldEnabled ) ;
143
+ Assert . Equal ( 1 , clone . X ) ;
144
+ Assert . Equal ( "a" , clone . Y ) ;
145
+ }
146
+
147
+ [ Fact ]
148
+ public void RoundTripPropertyOnlyPoco ( )
149
+ {
150
+ var source = new PropertyOnlyPoco { X = 1 , Y = "a" } ;
151
+ var clone = RoundTrip ( source , """{"X":1,"Y":"a"}"""u8 , JsonSerializer . Default ) ;
152
+ Assert . Equal ( 1 , clone . X ) ;
153
+ Assert . Equal ( "a" , clone . Y ) ;
154
+ }
155
+
156
+ [ Fact ]
157
+ public void RoundTripMixedPoco ( )
158
+ {
159
+ // this is the self-inflicted scenario; intent isn't obvious, so: we defer to STJ conventions,
160
+ // which means we lose the field
161
+ var source = new MixedFieldPropertyPoco { X = 1 , Y = "a" } ;
162
+ var clone = RoundTrip ( source , """{"Y":"a"}"""u8 , JsonSerializer . Default ) ;
163
+ Assert . Equal ( 0 , clone . X ) ; // <== drop
164
+ Assert . Equal ( "a" , clone . Y ) ;
165
+ }
166
+
167
+ [ Fact ]
168
+ public void RoundTripTree ( )
169
+ {
170
+ NodeA < string > source = new NodeA < string >
171
+ {
172
+ Value = "abc" ,
173
+ Next = new ( )
174
+ {
175
+ Value = "def"
176
+ }
177
+ } ;
178
+
179
+ var clone = RoundTrip ( source , """{"Next":{"Next":null,"Value":"def"},"Value":"abc"}"""u8 , JsonSerializer . Default ) ;
180
+ Assert . Equal ( "abc" , clone . Value ) ;
181
+ Assert . NotNull ( clone . Next ) ;
182
+ Assert . Equal ( "def" , clone . Next . Value ) ;
183
+ Assert . Null ( clone . Next . Next ) ;
184
+ }
185
+
186
+ [ Fact ]
187
+ public void RoundTripDictionary ( )
188
+ {
189
+ Dictionary < string , ( int id , string who , DateTime when ) > source = new ( )
190
+ {
191
+ [ "x" ] = ( 42 , "Fred" , new ( 2025 , 03 , 18 ) ) ,
192
+ [ "y" ] = ( 43 , "Barney" , new ( 2025 , 03 , 22 ) ) ,
193
+ } ;
194
+ var clone = RoundTrip ( source ,
195
+ """{"x":{"Item1":42,"Item2":"Fred","Item3":"2025-03-18T00:00:00"},"y":{"Item1":43,"Item2":"Barney","Item3":"2025-03-22T00:00:00"}}"""u8 ,
196
+ JsonSerializer . FieldEnabled ) ;
197
+ Assert . Equal ( 2 , clone . Count ) ;
198
+ Assert . True ( clone . TryGetValue ( "x" , out var val ) ) ;
199
+ Assert . Equal ( source [ "x" ] , val ) ;
200
+ Assert . True ( clone . TryGetValue ( "y" , out val ) ) ;
201
+ Assert . Equal ( source [ "y" ] , val ) ;
202
+ }
203
+
204
+ public class FieldOnlyPoco
205
+ {
206
+ public int X ;
207
+ public string ? Y ;
208
+ }
209
+
210
+ public class PropertyOnlyPoco
211
+ {
212
+ public int X { get ; set ; }
213
+ public string ? Y { get ; set ; }
214
+ }
215
+
216
+ public class MixedFieldPropertyPoco
217
+ {
218
+ public int X ; // field
219
+ public string ? Y { get ; set ; } // property
220
+ }
221
+
222
+ public class NodeA < T >
223
+ {
224
+ public NodeB < T > ? Next { get ; set ; }
225
+
226
+ public T ? Value { get ; set ; }
227
+ }
228
+
229
+ public class NodeB < T >
230
+ {
231
+ public NodeA < T > ? Next { get ; set ; }
232
+
233
+ public T ? Value { get ; set ; }
234
+ }
235
+
106
236
private static T RoundTrip < T > ( T value , ReadOnlySpan < byte > expectedBytes , JsonSerializer expectedJsonOptions , JsonSerializer addSerializers = JsonSerializer . None , bool binary = false )
107
237
{
108
238
var services = new ServiceCollection ( ) ;
@@ -112,13 +242,13 @@ private static T RoundTrip<T>(T value, ReadOnlySpan<byte> expectedBytes, JsonSer
112
242
113
243
if ( ( addSerializers & JsonSerializer . CustomGlobal ) != JsonSerializer . None )
114
244
{
115
- globalOptions = new ( ) ;
245
+ globalOptions = new ( ) { IncludeFields = true } ; // assume any custom options will serialize the whole type
116
246
services . AddKeyedSingleton < JsonSerializerOptions > ( typeof ( IHybridCacheSerializer < > ) , globalOptions ) ;
117
247
}
118
248
119
249
if ( ( addSerializers & JsonSerializer . CustomPerType ) != JsonSerializer . None )
120
250
{
121
- perTypeOptions = new ( ) ;
251
+ perTypeOptions = new ( ) { IncludeFields = true } ; // assume any custom options will serialize the whole type
122
252
services . AddKeyedSingleton < JsonSerializerOptions > ( typeof ( IHybridCacheSerializer < T > ) , perTypeOptions ) ;
123
253
}
124
254
0 commit comments