Skip to content

Commit f7ebef0

Browse files
kirk-marpleRehanSaeed
authored andcommitted
no message
1 parent b08b59c commit f7ebef0

File tree

1 file changed

+234
-78
lines changed

1 file changed

+234
-78
lines changed

Source/Schema.NET/ValuesJsonConverter.cs

Lines changed: 234 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)