33using System . Reflection ;
44using System . Text . Json . Serialization ;
55using System . Text . Json ;
6+ using System . Collections ;
67
78namespace Magic . IndexedDb . Models
89{
@@ -12,35 +13,99 @@ internal class MagicContractResolver<T> : JsonConverter<T>
1213
1314 public override T ? Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
1415 {
16+ // Defer deserialization back to built-in handling to ensure correctness
1517 return JsonSerializer . Deserialize < T > ( ref reader , options ) ;
1618 }
1719
1820 public override void Write ( Utf8JsonWriter writer , T value , JsonSerializerOptions options )
1921 {
20- if ( value == null ) return ;
21-
22- writer . WriteStartObject ( ) ;
22+ if ( value == null )
23+ {
24+ writer . WriteNullValue ( ) ;
25+ return ;
26+ }
2327
24- foreach ( var property in typeof ( T ) . GetProperties ( BindingFlags . Public | BindingFlags . Instance ) )
28+ try
2529 {
26- // Check cache first
27- if ( ! _cachedIgnoredProperties . TryGetValue ( property , out bool shouldIgnore ) )
30+ // Handle simple or primitive types directly
31+ if ( IsSimpleType ( typeof ( T ) ) )
2832 {
29- shouldIgnore = property . GetCustomAttributes ( inherit : true )
30- . Any ( a => a . GetType ( ) . FullName == typeof ( MagicNotMappedAttribute ) . FullName ) ;
31- _cachedIgnoredProperties [ property ] = shouldIgnore ;
33+ JsonSerializer . Serialize ( writer , value , options ) ;
34+ return ;
3235 }
3336
34- if ( ! shouldIgnore )
37+ // Handle collections
38+ if ( value is IEnumerable enumerable && typeof ( T ) != typeof ( string ) )
3539 {
36- var propValue = property . GetValue ( value ) ;
40+ writer . WriteStartArray ( ) ;
41+ foreach ( var item in enumerable )
42+ {
43+ if ( item == null )
44+ writer . WriteNullValue ( ) ;
45+ else
46+ JsonSerializer . Serialize ( writer , item , item . GetType ( ) , options ) ;
47+ }
48+ writer . WriteEndArray ( ) ;
49+ return ;
50+ }
51+
52+ // Handle complex objects
53+ writer . WriteStartObject ( ) ;
54+ foreach ( var property in typeof ( T ) . GetProperties ( BindingFlags . Public | BindingFlags . Instance ) )
55+ {
56+ if ( ShouldIgnoreProperty ( property ) )
57+ continue ;
58+
3759 var propName = options . PropertyNamingPolicy ? . ConvertName ( property . Name ) ?? property . Name ;
60+
61+ if ( property . GetIndexParameters ( ) . Length > 0 )
62+ continue ; // Skip indexers entirely
63+
64+ object ? propValue ;
65+ try
66+ {
67+ propValue = property . GetValue ( value ) ;
68+ }
69+ catch
70+ {
71+ // Fallback: write null if getting property fails
72+ propValue = null ;
73+ }
74+
3875 writer . WritePropertyName ( propName ) ;
39- JsonSerializer . Serialize ( writer , propValue , property . PropertyType , options ) ;
76+ if ( propValue == null )
77+ writer . WriteNullValue ( ) ;
78+ else
79+ JsonSerializer . Serialize ( writer , propValue , propValue . GetType ( ) , options ) ;
4080 }
81+ writer . WriteEndObject ( ) ;
82+ }
83+ catch
84+ {
85+ // Fallback to system serializer on complete failure
86+ JsonSerializer . Serialize ( writer , value , value . GetType ( ) , options ) ;
4187 }
88+ }
4289
43- writer . WriteEndObject ( ) ;
90+ private static bool ShouldIgnoreProperty ( PropertyInfo property )
91+ {
92+ return _cachedIgnoredProperties . GetOrAdd ( property , prop =>
93+ prop . GetCustomAttributes ( inherit : true )
94+ . Any ( a => a . GetType ( ) . FullName == typeof ( MagicNotMappedAttribute ) . FullName ) ) ;
95+ }
96+
97+ private static bool IsSimpleType ( Type type )
98+ {
99+ return type . IsPrimitive ||
100+ type . IsEnum ||
101+ type == typeof ( string ) ||
102+ type == typeof ( decimal ) ||
103+ type == typeof ( DateTime ) ||
104+ type == typeof ( DateTimeOffset ) ||
105+ type == typeof ( Guid ) ||
106+ type == typeof ( Uri ) ||
107+ type == typeof ( TimeSpan ) ;
44108 }
45109 }
110+
46111}
0 commit comments