using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json.Linq; namespace PettingZoo.Tapeti { internal class TypeToJObjectConverter { public static JObject Convert(Type type) { var result = new JObject(); foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { // Note: unfortunately we can not call GetCustomAttributes here, as that would // trigger assemblies not included in the package to be loaded var value = PropertyToJToken(propertyInfo.PropertyType); result.Add(propertyInfo.Name, value); } return result; } private static readonly Dictionary TypeMap = new() { { typeof(short), 0 }, { typeof(ushort), 0 }, { typeof(int), 0 }, { typeof(uint), 0 }, { typeof(long), 0 }, { typeof(ulong), 0 }, { typeof(decimal), 0.0 }, { typeof(float), 0.0 }, { typeof(bool), false } }; private static JToken PropertyToJToken(Type propertyType) { var actualType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; // String is also a class if (actualType == typeof(string)) return ""; if (actualType.IsClass) { // IEnumerable var enumerableInterface = actualType.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); if (enumerableInterface != null) return new JArray(Convert(enumerableInterface.GetGenericArguments()[0])); return Convert(actualType); } if (actualType.IsArray) return new JArray(Convert(actualType.GetElementType())); 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 TypeMap.TryGetValue(actualType, out var mappedToken) ? mappedToken : $"(unknown type: {actualType.Name})"; } } }