44using System . Text . Encodings . Web ;
55using System . Text . Json ;
66using System . Text . Json . Serialization ;
7+ using JsonApiDotNetCore . Configuration ;
8+ using JsonApiDotNetCore . Resources ;
9+ using JsonApiDotNetCore . Resources . Annotations ;
710using Microsoft . Extensions . Logging ;
811
912namespace JsonApiDotNetCore . Middleware ;
@@ -14,8 +17,105 @@ internal abstract class TraceLogWriter
1417 {
1518 WriteIndented = true ,
1619 Encoder = JavaScriptEncoder . UnsafeRelaxedJsonEscaping ,
17- ReferenceHandler = ReferenceHandler . Preserve
20+ ReferenceHandler = ReferenceHandler . IgnoreCycles ,
21+ DefaultIgnoreCondition = JsonIgnoreCondition . WhenWritingNull ,
22+ Converters =
23+ {
24+ new JsonStringEnumConverter ( ) ,
25+ new ResourceTypeInTraceJsonConverter ( ) ,
26+ new ResourceFieldInTraceJsonConverterFactory ( ) ,
27+ new AbstractResourceWrapperInTraceJsonConverterFactory ( ) ,
28+ new IdentifiableInTraceJsonConverter ( )
29+ }
1830 } ;
31+
32+ private sealed class ResourceTypeInTraceJsonConverter : JsonConverter < ResourceType >
33+ {
34+ public override ResourceType Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
35+ {
36+ throw new NotSupportedException ( ) ;
37+ }
38+
39+ public override void Write ( Utf8JsonWriter writer , ResourceType value , JsonSerializerOptions options )
40+ {
41+ writer . WriteStringValue ( value . PublicName ) ;
42+ }
43+ }
44+
45+ private sealed class ResourceFieldInTraceJsonConverterFactory : JsonConverterFactory
46+ {
47+ public override bool CanConvert ( Type typeToConvert )
48+ {
49+ return typeToConvert . IsAssignableTo ( typeof ( ResourceFieldAttribute ) ) ;
50+ }
51+
52+ public override JsonConverter CreateConverter ( Type typeToConvert , JsonSerializerOptions options )
53+ {
54+ Type converterType = typeof ( ResourceFieldInTraceJsonConverter < > ) . MakeGenericType ( typeToConvert ) ;
55+ return ( JsonConverter ) Activator . CreateInstance ( converterType ) ! ;
56+ }
57+
58+ private sealed class ResourceFieldInTraceJsonConverter < TField > : JsonConverter < TField >
59+ where TField : ResourceFieldAttribute
60+ {
61+ public override TField Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
62+ {
63+ throw new NotSupportedException ( ) ;
64+ }
65+
66+ public override void Write ( Utf8JsonWriter writer , TField value , JsonSerializerOptions options )
67+ {
68+ writer . WriteStringValue ( value . PublicName ) ;
69+ }
70+ }
71+ }
72+
73+ private sealed class IdentifiableInTraceJsonConverter : JsonConverter < IIdentifiable >
74+ {
75+ public override IIdentifiable Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
76+ {
77+ throw new NotSupportedException ( ) ;
78+ }
79+
80+ public override void Write ( Utf8JsonWriter writer , IIdentifiable value , JsonSerializerOptions options )
81+ {
82+ // Intentionally *not* calling GetClrType() because we need delegation to the wrapper converter.
83+ Type runtimeType = value . GetType ( ) ;
84+
85+ JsonSerializer . Serialize ( writer , value , runtimeType , options ) ;
86+ }
87+ }
88+
89+ private sealed class AbstractResourceWrapperInTraceJsonConverterFactory : JsonConverterFactory
90+ {
91+ public override bool CanConvert ( Type typeToConvert )
92+ {
93+ return typeToConvert . IsAssignableTo ( typeof ( IAbstractResourceWrapper ) ) ;
94+ }
95+
96+ public override JsonConverter CreateConverter ( Type typeToConvert , JsonSerializerOptions options )
97+ {
98+ Type converterType = typeof ( AbstractResourceWrapperInTraceJsonConverter < > ) . MakeGenericType ( typeToConvert ) ;
99+ return ( JsonConverter ) Activator . CreateInstance ( converterType ) ! ;
100+ }
101+
102+ private sealed class AbstractResourceWrapperInTraceJsonConverter < TWrapper > : JsonConverter < TWrapper >
103+ where TWrapper : IAbstractResourceWrapper
104+ {
105+ public override TWrapper Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
106+ {
107+ throw new NotSupportedException ( ) ;
108+ }
109+
110+ public override void Write ( Utf8JsonWriter writer , TWrapper value , JsonSerializerOptions options )
111+ {
112+ writer . WriteStartObject ( ) ;
113+ writer . WriteString ( "ClrType" , value . AbstractType . FullName ) ;
114+ writer . WriteString ( "StringId" , value . StringId ) ;
115+ writer . WriteEndObject ( ) ;
116+ }
117+ }
118+ }
19119}
20120
21121internal sealed class TraceLogWriter < T > : TraceLogWriter
@@ -88,26 +188,12 @@ private static void WriteProperty(StringBuilder builder, PropertyInfo property,
88188 builder . Append ( ": " ) ;
89189
90190 object ? value = property . GetValue ( instance ) ;
91-
92- if ( value == null )
93- {
94- builder . Append ( "null" ) ;
95- }
96- else if ( value is string stringValue )
97- {
98- builder . Append ( '"' ) ;
99- builder . Append ( stringValue ) ;
100- builder . Append ( '"' ) ;
101- }
102- else
103- {
104- WriteObject ( builder , value ) ;
105- }
191+ WriteObject ( builder , value ) ;
106192 }
107193
108- private static void WriteObject ( StringBuilder builder , object value )
194+ private static void WriteObject ( StringBuilder builder , object ? value )
109195 {
110- if ( HasToStringOverload ( value . GetType ( ) ) )
196+ if ( value != null && value is not string && HasToStringOverload ( value . GetType ( ) ) )
111197 {
112198 builder . Append ( value ) ;
113199 }
@@ -118,28 +204,19 @@ private static void WriteObject(StringBuilder builder, object value)
118204 }
119205 }
120206
121- private static bool HasToStringOverload ( Type ? type )
207+ private static bool HasToStringOverload ( Type type )
122208 {
123- if ( type != null )
124- {
125- MethodInfo ? toStringMethod = type . GetMethod ( "ToString" , Array . Empty < Type > ( ) ) ;
126-
127- if ( toStringMethod != null && toStringMethod . DeclaringType != typeof ( object ) )
128- {
129- return true ;
130- }
131- }
132-
133- return false ;
209+ MethodInfo ? toStringMethod = type . GetMethod ( "ToString" , Array . Empty < Type > ( ) ) ;
210+ return toStringMethod != null && toStringMethod . DeclaringType != typeof ( object ) ;
134211 }
135212
136- private static string SerializeObject ( object value )
213+ private static string SerializeObject ( object ? value )
137214 {
138215 try
139216 {
140217 return JsonSerializer . Serialize ( value , SerializerOptions ) ;
141218 }
142- catch ( JsonException )
219+ catch ( Exception exception ) when ( exception is JsonException or NotSupportedException )
143220 {
144221 // Never crash as a result of logging, this is best-effort only.
145222 return "object" ;
0 commit comments