1
0
mirror of synced 2024-11-14 17:33:49 +00:00
PettingZoo/PettingZoo.Tapeti/TypeToJObjectConverter.cs
Mark van Renswoude c75ea0cc62 Implemented DataAnnotation based validation for Tapeti messages
Fixed class name missing when selecting from Tapeti class
Added Filter to class selection dialog
Fixed or removed some minor todo's
2022-01-03 15:04:00 +01:00

105 lines
3.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq;
namespace PettingZoo.Tapeti
{
public class TypeToJObjectConverter
{
public static JObject Convert(Type type)
{
if (!type.IsClass)
throw new ArgumentException($"TypeToJObjectConverter.Convert expects a class, got {type.Name}");
return ClassToJToken(type, Array.Empty<Type>());
}
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) }
};
private static readonly Dictionary<Type, JToken> TypeValueMap = new()
{
{ typeof(int), 0 },
{ typeof(decimal), 0.0 },
{ typeof(bool), false }
};
private static JObject ClassToJToken(Type classType, IEnumerable<Type> typesEncountered)
{
var newTypesEncountered = typesEncountered.Append(classType).ToArray();
var result = new JObject();
foreach (var propertyInfo in classType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
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;
// TODO check for JsonConverter attribute? doubt we'll be able to generate a nice value for it, but at least we can provide a placeholder
// String is also a class
if (actualType == typeof(string))
return "";
if (actualType.IsClass)
{
// IEnumerable<T>
var enumerableInterface = actualType.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (enumerableInterface != null)
return new JArray(TypeToJToken(enumerableInterface.GetGenericArguments()[0], typesEncountered));
return typesEncountered.Contains(actualType) ? new JValue((object?)null) : ClassToJToken(actualType, typesEncountered);
}
if (actualType.IsArray)
return new JArray(TypeToJToken(actualType.GetElementType()!, typesEncountered));
if (actualType.IsEnum)
return Enum.GetNames(actualType).FirstOrDefault();
// 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();
return TypeValueMap.TryGetValue(actualType, out var mappedToken)
? mappedToken
: $"(unknown type: {actualType.Name})";
}
}
}