2021-11-28 16:50:17 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
|
2021-12-15 09:50:45 +00:00
|
|
|
|
namespace PettingZoo.Tapeti
|
2021-11-28 16:50:17 +00:00
|
|
|
|
{
|
2021-12-31 17:48:04 +00:00
|
|
|
|
public class TypeToJObjectConverter
|
2021-11-28 16:50:17 +00:00
|
|
|
|
{
|
2021-12-15 09:50:45 +00:00
|
|
|
|
public static JObject Convert(Type type)
|
2021-11-28 16:50:17 +00:00
|
|
|
|
{
|
2022-01-01 20:45:15 +00:00
|
|
|
|
if (!type.IsClass)
|
|
|
|
|
throw new ArgumentException($"TypeToJObjectConverter.Convert expects a class, got {type.Name}");
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
2022-01-01 20:45:15 +00:00
|
|
|
|
return ClassToJToken(type, Array.Empty<Type>());
|
|
|
|
|
}
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
2021-11-28 16:50:17 +00:00
|
|
|
|
|
2022-01-01 20:45:15 +00:00
|
|
|
|
private static readonly Dictionary<Type, Type> TypeEquivalenceMap = new()
|
|
|
|
|
{
|
|
|
|
|
{ typeof(uint), typeof(int) },
|
|
|
|
|
{ typeof(long), typeof(int) },
|
|
|
|
|
{ typeof(ulong), typeof(int) },
|
|
|
|
|
{ typeof(short), typeof(int) },
|
|
|
|
|
{ typeof(ushort), typeof(int) },
|
|
|
|
|
{ typeof(float), typeof(decimal) }
|
|
|
|
|
};
|
2021-11-28 16:50:17 +00:00
|
|
|
|
|
|
|
|
|
|
2022-01-01 20:45:15 +00:00
|
|
|
|
private static readonly Dictionary<Type, JToken> TypeValueMap = new()
|
2021-11-28 16:50:17 +00:00
|
|
|
|
{
|
|
|
|
|
{ typeof(int), 0 },
|
|
|
|
|
{ typeof(decimal), 0.0 },
|
|
|
|
|
{ typeof(bool), false }
|
|
|
|
|
};
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
|
|
|
|
|
2022-01-01 20:45:15 +00:00
|
|
|
|
private static JObject ClassToJToken(Type classType, IEnumerable<Type> typesEncountered)
|
2021-11-28 16:50:17 +00:00
|
|
|
|
{
|
2022-01-01 20:45:15 +00:00
|
|
|
|
var newTypesEncountered = typesEncountered.Append(classType).ToArray();
|
|
|
|
|
var result = new JObject();
|
|
|
|
|
|
|
|
|
|
foreach (var propertyInfo in classType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
|
|
|
|
{
|
|
|
|
|
// Note: unfortunately we can not call GetCustomAttributes here for now, as that would
|
|
|
|
|
// trigger assemblies not included in the package to be loaded, which may not exist
|
|
|
|
|
|
|
|
|
|
var value = TypeToJToken(propertyInfo.PropertyType, newTypesEncountered);
|
|
|
|
|
result.Add(propertyInfo.Name, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static JToken TypeToJToken(Type type, ICollection<Type> typesEncountered)
|
|
|
|
|
{
|
|
|
|
|
var actualType = Nullable.GetUnderlyingType(type) ?? type;
|
|
|
|
|
|
|
|
|
|
if (TypeEquivalenceMap.TryGetValue(actualType, out var equivalentType))
|
|
|
|
|
actualType = equivalentType;
|
2021-11-28 16:50:17 +00:00
|
|
|
|
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
2021-11-28 16:50:17 +00:00
|
|
|
|
// String is also a class
|
|
|
|
|
if (actualType == typeof(string))
|
|
|
|
|
return "";
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
|
|
|
|
|
2021-11-28 16:50:17 +00:00
|
|
|
|
if (actualType.IsClass)
|
|
|
|
|
{
|
|
|
|
|
// IEnumerable<T>
|
|
|
|
|
var enumerableInterface = actualType.GetInterfaces()
|
|
|
|
|
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
2021-11-28 16:50:17 +00:00
|
|
|
|
if (enumerableInterface != null)
|
2022-01-01 20:45:15 +00:00
|
|
|
|
return new JArray(TypeToJToken(enumerableInterface.GetGenericArguments()[0], typesEncountered));
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
2022-01-01 20:45:15 +00:00
|
|
|
|
return typesEncountered.Contains(actualType) ? new JValue((object?)null) : ClassToJToken(actualType, typesEncountered);
|
2021-11-28 16:50:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actualType.IsArray)
|
2022-01-01 20:45:15 +00:00
|
|
|
|
return new JArray(TypeToJToken(actualType.GetElementType()!, typesEncountered));
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
2021-11-28 16:50:17 +00:00
|
|
|
|
if (actualType.IsEnum)
|
|
|
|
|
return Enum.GetNames(actualType).FirstOrDefault();
|
|
|
|
|
|
2021-12-15 09:50:45 +00:00
|
|
|
|
|
2021-11-28 16:50:17 +00:00
|
|
|
|
// Special cases for runtime generated values
|
|
|
|
|
if (actualType == typeof(DateTime))
|
|
|
|
|
{
|
|
|
|
|
// Strip the milliseconds for a cleaner result
|
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
|
return new DateTime(now.Ticks - now.Ticks % TimeSpan.TicksPerSecond, now.Kind);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actualType == typeof(Guid))
|
|
|
|
|
return Guid.NewGuid().ToString();
|
|
|
|
|
|
2022-01-01 20:45:15 +00:00
|
|
|
|
return TypeValueMap.TryGetValue(actualType, out var mappedToken)
|
2021-11-28 16:50:17 +00:00
|
|
|
|
? mappedToken
|
|
|
|
|
: $"(unknown type: {actualType.Name})";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|