7
7
using System ;
8
8
using System . Reflection ;
9
9
using System . Collections ;
10
+ using System . Collections . Concurrent ;
11
+ using System . Linq ;
10
12
11
13
namespace Microsoft . Agents . Core . Serialization . Converters
12
14
{
13
15
public abstract class ConnectorConverter < T > : JsonConverter < T > where T : new ( )
14
16
{
17
+ private static readonly ConcurrentDictionary < Type , PropertyInfo [ ] > PropertyCache = new ( ) ;
18
+ private static readonly ConcurrentDictionary < ( Type , bool , Type ) , Dictionary < string , ( PropertyInfo , bool ) > > JsonPropertyMetadataCache = new ( ) ;
19
+
15
20
public override T Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
16
21
{
17
22
if ( reader . TokenType != JsonTokenType . StartObject )
@@ -21,14 +26,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
21
26
22
27
var value = new T ( ) ;
23
28
24
- var properties = options . PropertyNameCaseInsensitive
25
- ? new Dictionary < string , PropertyInfo > ( StringComparer . OrdinalIgnoreCase )
26
- : new Dictionary < string , PropertyInfo > ( ) ;
27
-
28
- foreach ( var property in typeof ( T ) . GetProperties ( ) )
29
- {
30
- properties . Add ( property . Name , property ) ;
31
- }
29
+ var propertyMetadataMap = GetJsonPropertyMetadata ( typeof ( T ) , options . PropertyNameCaseInsensitive , options . PropertyNamingPolicy ) ;
32
30
33
31
while ( reader . Read ( ) )
34
32
{
@@ -41,9 +39,9 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
41
39
{
42
40
var propertyName = reader . GetString ( ) ;
43
41
44
- if ( properties . ContainsKey ( propertyName ) )
42
+ if ( propertyMetadataMap . TryGetValue ( propertyName , out var entry ) )
45
43
{
46
- ReadProperty ( ref reader , value , propertyName , options , properties ) ;
44
+ ReadProperty ( ref reader , value , propertyName , options , entry . Property ) ;
47
45
}
48
46
else
49
47
{
@@ -59,8 +57,18 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions
59
57
{
60
58
writer . WriteStartObject ( ) ;
61
59
62
- foreach ( var property in value . GetType ( ) . GetProperties ( ) )
60
+ var type = value . GetType ( ) ;
61
+ var properties = GetCachedProperties ( type ) ;
62
+ var propertyMetadataMap = GetJsonPropertyMetadata ( type , false , options . PropertyNamingPolicy ) ; // case-insensitivity doesn’t matter here
63
+ var reverseMap = propertyMetadataMap . ToDictionary ( kv => kv . Value . Property , kv => ( kv . Key , kv . Value . IsIgnored ) ) ;
64
+
65
+ foreach ( var property in properties )
63
66
{
67
+ if ( ! reverseMap . TryGetValue ( property , out var propertyMetadata ) || propertyMetadata . IsIgnored )
68
+ {
69
+ continue ;
70
+ }
71
+
64
72
if ( ! TryWriteExtensionData ( writer , value , property . Name ) )
65
73
{
66
74
var propertyValue = property . GetValue ( value ) ;
@@ -77,9 +85,7 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions
77
85
if ( propertyValue != null || ! ( options . DefaultIgnoreCondition == JsonIgnoreCondition . WhenWritingNull ) )
78
86
79
87
{
80
- var propertyName = options . PropertyNamingPolicy == JsonNamingPolicy . CamelCase
81
- ? JsonNamingPolicy . CamelCase . ConvertName ( property . Name )
82
- : property . Name ;
88
+ var propertyName = propertyMetadata . Key ?? property . Name ;
83
89
84
90
writer . WritePropertyName ( propertyName ) ;
85
91
@@ -254,10 +260,8 @@ protected void SetGenericProperty(ref Utf8JsonReader reader, Action<object> sett
254
260
setter ( deserialized ) ;
255
261
}
256
262
257
- private void ReadProperty ( ref Utf8JsonReader reader , T value , string propertyName , JsonSerializerOptions options , Dictionary < string , PropertyInfo > properties )
263
+ private void ReadProperty ( ref Utf8JsonReader reader , T value , string propertyName , JsonSerializerOptions options , PropertyInfo property )
258
264
{
259
- var property = properties [ propertyName ] ;
260
-
261
265
if ( TryReadExtensionData ( ref reader , value , property . Name , options ) )
262
266
{
263
267
return ;
@@ -292,5 +296,39 @@ private void ReadProperty(ref Utf8JsonReader reader, T value, string propertyNam
292
296
var propertyValue = System . Text . Json . JsonSerializer . Deserialize ( ref reader , property . PropertyType , options ) ;
293
297
property . SetValue ( value , propertyValue ) ;
294
298
}
299
+
300
+ private static PropertyInfo [ ] GetCachedProperties ( Type type )
301
+ {
302
+ return PropertyCache . GetOrAdd ( type , static t => t . GetProperties ( ) ) ;
303
+ }
304
+
305
+ private static Dictionary < string , ( PropertyInfo Property , bool IsIgnored ) > GetJsonPropertyMetadata ( Type type , bool caseInsensitive , JsonNamingPolicy ? namingPolicy )
306
+ {
307
+ var cacheKey = ( type , caseInsensitive , namingPolicy ? . GetType ( ) ) ;
308
+ return JsonPropertyMetadataCache . GetOrAdd ( cacheKey , key =>
309
+ {
310
+ var ( t , insensitive , _) = key ;
311
+ var comparer = insensitive ? StringComparer . OrdinalIgnoreCase : StringComparer . Ordinal ;
312
+ var metadata = new Dictionary < string , ( PropertyInfo , bool ) > ( comparer ) ;
313
+
314
+ foreach ( var prop in GetCachedProperties ( t ) )
315
+ {
316
+ var resolvedName = prop . GetCustomAttribute < JsonPropertyNameAttribute > ( ) ? . Name
317
+ ?? namingPolicy ? . ConvertName ( prop . Name )
318
+ ?? prop . Name ;
319
+
320
+ if ( metadata . ContainsKey ( resolvedName ) )
321
+ {
322
+ throw new InvalidOperationException (
323
+ $ "Duplicate JSON property name detected: '{ resolvedName } ' maps to multiple properties in type '{ t . FullName } '."
324
+ ) ;
325
+ }
326
+
327
+ metadata [ resolvedName ] = ( prop , prop . GetCustomAttribute < JsonIgnoreAttribute > ( ) ? . Condition == JsonIgnoreCondition . Always ) ;
328
+ }
329
+
330
+ return metadata ;
331
+ } ) ;
332
+ }
295
333
}
296
334
}
0 commit comments