@@ -45,44 +45,20 @@ public override object ReadJson(
4545 object argument = null ;
4646
4747 var tokenType = reader . TokenType ;
48- var value = SanitizeReaderValue ( reader , tokenType ) ;
48+ var value = reader . Value ;
4949
5050 var token = JToken . Load ( reader ) ;
5151 if ( mainType . GenericTypeArguments . Length == 1 )
5252 {
53- var type = mainType . GenericTypeArguments [ 0 ] . GetUnderlyingTypeFromNullable ( ) ;
53+ var type = mainType . GenericTypeArguments [ 0 ] ;
5454 if ( tokenType == JsonToken . StartArray )
5555 {
56- argument = ReadJsonArray ( token , type ) ;
57- }
58- else if ( type . IsPrimitiveType ( ) )
59- {
60- argument = value ;
61- }
62- else if ( type == typeof ( decimal ) )
63- {
64- argument = Convert . ToDecimal ( value , CultureInfo . InvariantCulture ) ;
56+ var unwrappedType = type . GetUnderlyingTypeFromNullable ( ) ;
57+ argument = ReadJsonArray ( token , unwrappedType ) ;
6558 }
6659 else
6760 {
68- if ( type . GetTypeInfo ( ) . IsEnum )
69- {
70- var enumString = token . ToString ( ) . Substring ( "http://schema.org/" . Length ) ;
71- argument = Enum . Parse ( type , enumString ) ;
72- }
73- else
74- {
75- var typeName = GetTypeNameFromToken ( token ) ;
76- if ( string . IsNullOrEmpty ( typeName ) )
77- {
78- argument = token . ToObject ( type ) ;
79- }
80- else
81- {
82- var builtType = Type . GetType ( $ "{ NamespacePrefix } { typeName } ") ;
83- argument = token . ToObject ( builtType ) ;
84- }
85- }
61+ argument = ParseTokenArguments ( token , tokenType , type , value ) ;
8662 }
8763 }
8864 else
@@ -92,7 +68,8 @@ public override object ReadJson(
9268 var items = new List < object > ( ) ;
9369 foreach ( var type in mainType . GenericTypeArguments )
9470 {
95- var args = ReadJsonArray ( token , type ) ;
71+ var unwrappedType = type . GetUnderlyingTypeFromNullable ( ) ;
72+ var args = ReadJsonArray ( token , unwrappedType ) ;
9673 var genericType = typeof ( OneOrMany < > ) . MakeGenericType ( type ) ;
9774 var item = ( IValues ) Activator . CreateInstance ( genericType , args ) ;
9875 items . Add ( item ) ;
@@ -102,54 +79,20 @@ public override object ReadJson(
10279 }
10380 else
10481 {
105- foreach ( var type in mainType . GenericTypeArguments )
82+ for ( var i = mainType . GenericTypeArguments . Length - 1 ; i >= 0 ; i -- )
10683 {
84+ var type = mainType . GenericTypeArguments [ i ] ;
85+ object args = null ;
86+
10787 try
10888 {
109- object args ;
110- if ( tokenType == JsonToken . StartObject )
111- {
112- var typeName = GetTypeNameFromToken ( token ) ;
113- if ( string . IsNullOrEmpty ( typeName ) )
114- {
115- args = token . ToObject ( type ) ;
116- }
117- else if ( typeName == type . Name )
118- {
119- args = token . ToObject ( type ) ;
120- }
121- else
122- {
123- var builtType = Type . GetType ( $ "{ NamespacePrefix } { typeName } ") ;
124- if ( builtType != null && type . GetTypeInfo ( ) . IsAssignableFrom ( builtType . GetTypeInfo ( ) ) )
125- {
126- args = token . ToObject ( builtType ) ;
127- }
128- else
129- {
130- continue ;
131- }
132- }
133- }
134- else
89+ args = ParseTokenArguments ( token , tokenType , type , value ) ;
90+
91+ if ( args != null )
13592 {
136- var unwrappedType = type . GetUnderlyingTypeFromNullable ( ) ;
137- if ( unwrappedType . IsPrimitiveType ( ) )
138- {
139- args = value ;
140- }
141- else if ( unwrappedType == typeof ( decimal ) )
142- {
143- args = Convert . ToDecimal ( value , CultureInfo . InvariantCulture ) ;
144- }
145- else
146- {
147- args = token . ToObject ( ToClass ( type ) ) ; // This is expected to throw on some case
148- }
93+ var genericType = typeof ( OneOrMany < > ) . MakeGenericType ( type ) ;
94+ argument = Activator . CreateInstance ( genericType , args ) ;
14995 }
150-
151- var genericType = typeof ( OneOrMany < > ) . MakeGenericType ( type ) ;
152- argument = Activator . CreateInstance ( genericType , args ) ;
15396 }
15497#pragma warning disable CA1031 // Do not catch general exception types
15598 catch ( Exception e )
@@ -159,6 +102,12 @@ public override object ReadJson(
159102 Debug . WriteLine ( e ) ;
160103 }
161104#pragma warning restore CA1031 // Do not catch general exception types
105+
106+ if ( argument != null )
107+ {
108+ // return first valid argument, going from right to left in generic type arguments
109+ break ;
110+ }
162111 }
163112 }
164113 }
@@ -212,6 +161,216 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
212161 public virtual void WriteObject ( JsonWriter writer , object value , JsonSerializer serializer ) =>
213162 serializer . Serialize ( writer , value ) ;
214163
164+ private static object ParseTokenArguments ( JToken token , JsonToken tokenType , Type type , object value )
165+ {
166+ const int SCHEMA_ORG_LENGTH = 18 ;
167+ object args = null ;
168+ var unwrappedType = type . GetUnderlyingTypeFromNullable ( ) ;
169+ if ( unwrappedType . GetTypeInfo ( ) . IsEnum )
170+ {
171+ var enumString = token . ToString ( ) . Substring ( SCHEMA_ORG_LENGTH ) ;
172+ args = Enum . Parse ( unwrappedType , enumString ) ;
173+ }
174+ else
175+ {
176+ if ( tokenType == JsonToken . StartObject )
177+ {
178+ args = ParseTokenObjectArguments ( token , type , unwrappedType ) ;
179+ }
180+ else
181+ {
182+ args = ParseTokenValueArguments ( token , tokenType , type , unwrappedType , value ) ;
183+ }
184+ }
185+
186+ return args ;
187+ }
188+
189+ private static object ParseTokenObjectArguments ( JToken token , Type type , Type unwrappedType )
190+ {
191+ object args = null ;
192+ var typeName = GetTypeNameFromToken ( token ) ;
193+ if ( string . IsNullOrEmpty ( typeName ) )
194+ {
195+ args = token . ToObject ( unwrappedType ) ;
196+ }
197+ else if ( typeName == type . Name )
198+ {
199+ args = token . ToObject ( type ) ;
200+ }
201+ else
202+ {
203+ var builtType = Type . GetType ( $ "{ NamespacePrefix } { typeName } ") ;
204+ if ( builtType != null && type . GetTypeInfo ( ) . IsAssignableFrom ( builtType . GetTypeInfo ( ) ) )
205+ {
206+ args = token . ToObject ( builtType ) ;
207+ }
208+ }
209+
210+ return args ;
211+ }
212+
213+ private static object ParseTokenValueArguments ( JToken token , JsonToken tokenType , Type type , Type unwrappedType , object value )
214+ {
215+ object args = null ;
216+ if ( unwrappedType . IsPrimitiveType ( ) )
217+ {
218+ if ( value is string )
219+ {
220+ if ( unwrappedType == typeof ( string ) )
221+ {
222+ args = value ;
223+ }
224+ else if ( unwrappedType == typeof ( int ) )
225+ {
226+ if ( int . TryParse ( ( string ) value , NumberStyles . Integer , CultureInfo . InvariantCulture , out var i ) )
227+ {
228+ args = i ;
229+ }
230+ }
231+ else if ( unwrappedType == typeof ( long ) )
232+ {
233+ if ( long . TryParse ( ( string ) value , NumberStyles . Integer , CultureInfo . InvariantCulture , out var i ) )
234+ {
235+ args = i ;
236+ }
237+ }
238+ else if ( unwrappedType == typeof ( float ) )
239+ {
240+ if ( float . TryParse ( ( string ) value , NumberStyles . Float , CultureInfo . InvariantCulture , out var i ) )
241+ {
242+ args = i ;
243+ }
244+ }
245+ else if ( unwrappedType == typeof ( double ) )
246+ {
247+ if ( double . TryParse ( ( string ) value , NumberStyles . Float , CultureInfo . InvariantCulture , out var i ) )
248+ {
249+ args = i ;
250+ }
251+ }
252+ else if ( unwrappedType == typeof ( bool ) )
253+ {
254+ if ( bool . TryParse ( ( string ) value , out var i ) )
255+ {
256+ args = i ;
257+ }
258+ }
259+ }
260+ else if ( value is short || value is int || value is long || value is float || value is double )
261+ {
262+ // Can safely convert between numeric types
263+ if ( unwrappedType == typeof ( short ) || unwrappedType == typeof ( int ) || unwrappedType == typeof ( long ) || unwrappedType == typeof ( float ) || unwrappedType == typeof ( double ) )
264+ {
265+ args = Convert . ChangeType ( value , unwrappedType , CultureInfo . InvariantCulture ) ;
266+ }
267+ }
268+ else if ( value is bool )
269+ {
270+ if ( unwrappedType == typeof ( bool ) )
271+ {
272+ args = value ;
273+ }
274+ }
275+ else if ( value is DateTime || value is DateTimeOffset )
276+ {
277+ // NO-OP: can't put a date into a primitive type
278+ }
279+ else
280+ {
281+ args = value ;
282+ }
283+ }
284+ else if ( unwrappedType == typeof ( decimal ) )
285+ {
286+ if ( value is string )
287+ {
288+ if ( decimal . TryParse ( ( string ) value , NumberStyles . Currency , CultureInfo . InvariantCulture , out var i ) )
289+ {
290+ args = i ;
291+ }
292+ }
293+ else
294+ {
295+ args = Convert . ToDecimal ( value , CultureInfo . InvariantCulture ) ;
296+ }
297+ }
298+ else if ( unwrappedType == typeof ( DateTime ) )
299+ {
300+ if ( value is string )
301+ {
302+ if ( DateTime . TryParse ( ( string ) value , CultureInfo . InvariantCulture , DateTimeStyles . None , out var i ) )
303+ {
304+ args = i ;
305+ }
306+ }
307+ else if ( value is DateTime )
308+ {
309+ args = value ;
310+ }
311+ else if ( value is DateTimeOffset )
312+ {
313+ args = ( ( DateTimeOffset ) value ) . DateTime ;
314+ }
315+ else if ( value is short || value is int || value is long || value is float || value is double )
316+ {
317+ // NO-OP: can't put a primitive type into a date
318+ }
319+ else
320+ {
321+ args = Convert . ToDateTime ( value , CultureInfo . InvariantCulture ) ;
322+ }
323+ }
324+ else if ( unwrappedType == typeof ( DateTimeOffset ) )
325+ {
326+ if ( value is string )
327+ {
328+ if ( DateTimeOffset . TryParse ( ( string ) value , CultureInfo . InvariantCulture , DateTimeStyles . None , out var i ) )
329+ {
330+ args = i ;
331+ }
332+ }
333+ else if ( value is DateTime )
334+ {
335+ args = new DateTimeOffset ( ( DateTime ) value ) ;
336+ }
337+ else if ( value is DateTimeOffset )
338+ {
339+ args = value ;
340+ }
341+ else
342+ {
343+ args = Convert . ToDateTime ( value , CultureInfo . InvariantCulture ) ;
344+ }
345+ }
346+ else
347+ {
348+ var classType = ToClass ( type ) ;
349+ if ( tokenType == JsonToken . String )
350+ {
351+ if ( classType == typeof ( Uri ) )
352+ {
353+ // REVIEW: Avoid invalid URIs being assigned as URI (Should we only allow absolute URIs?)
354+ if ( Uri . TryCreate ( ( string ) value , UriKind . Absolute , out var i ) )
355+ {
356+ args = i ;
357+ }
358+ }
359+ }
360+
361+ // REVIEW: If argument still not assigned, only use ToObject if not casting primitive to interface or class
362+ if ( args == null )
363+ {
364+ if ( ! type . GetTypeInfo ( ) . IsInterface && ! type . GetTypeInfo ( ) . IsClass )
365+ {
366+ args = token . ToObject ( classType ) ; // This is expected to throw on some case
367+ }
368+ }
369+ }
370+
371+ return args ;
372+ }
373+
215374 /// <summary>
216375 /// Gets the class type definition.
217376 /// </summary>
@@ -278,13 +437,10 @@ private static IEnumerable<Type> GetTypeHierarchy(Type type)
278437 }
279438 }
280439
281- private static object SanitizeReaderValue ( JsonReader reader , JsonToken tokenType ) =>
282- tokenType == JsonToken . Integer ? Convert . ToInt32 ( reader . Value , CultureInfo . InvariantCulture ) : reader . Value ;
283-
284440 private static string GetTypeNameFromToken ( JToken token )
285441 {
286- var typeNameToken = token . Values ( ) . FirstOrDefault ( t => t . Path . EndsWith ( "@type" , StringComparison . Ordinal ) ) ;
287- return typeNameToken ? . Value < string > ( ) ;
442+ var o = token as JObject ;
443+ return o ? . SelectToken ( "@type" ) ? . ToString ( ) ;
288444 }
289445 }
290446}
0 commit comments