Files
Caleb Sandford deQuincey ecdd3e2a9e intial commit
2025-06-27 23:27:49 +01:00

224 lines
8.4 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.ConstrainedExecution;
namespace UnityEditor.PackageManager.ValidationSuite
{
internal class JsonUnmarshaller
{
private static readonly MethodInfo GetValForFieldMethod = typeof(JsonUnmarshaller).GetMethod(nameof(GetValueForFieldInternal), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo GetListMethod = typeof(JsonUnmarshaller).GetMethod(nameof(GetList), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo GetDictionaryMethod = typeof(JsonUnmarshaller).GetMethod(nameof(GetDictionary), BindingFlags.Static | BindingFlags.NonPublic);
private static Stack<string> fieldStack = new Stack<string>();
public static T GetValue<T>(object val, ref List<UnmarshallingException> unmarshallingErrors)
{
return GetValueInternal<T>(val, ref unmarshallingErrors);
}
private static T GetValueForFieldInternal<T>(object val, string fieldName, ref List<UnmarshallingException> unmarshallingErrors)
{
try
{
fieldStack.Push(fieldName);
return GetValueInternal<T>(val, ref unmarshallingErrors);
}
finally
{
fieldStack.Pop();
}
}
private static bool HasConversionOperator(Type from, Type to)
{
try
{
var inp = Expression.Parameter(from, "inp");
// If this succeeds then we can cast 'from' type to 'to' type using implicit coercion
Expression.Lambda(Expression.Convert(inp, to), inp).Compile();
return true;
}
catch (InvalidOperationException)
{
return false;
}
}
private static T GetValueInternal<T>(object val, ref List<UnmarshallingException> unmarshallingErrors)
{
if (val == null)
{
return default;
}
var type = typeof(T);
if (type.IsPrimitive || type == typeof(string))
{
if (!HasConversionOperator(val.GetType(), type))
{
unmarshallingErrors.Add(new UnmarshallingException(string.Join(".", fieldStack), type, val.GetType()));
return default;
}
var convertedType = (T)Convert.ChangeType(val, type);
return convertedType;
}
if (typeof(IList).IsAssignableFrom(type))
{
var getValFunc = GetListMethod.MakeGenericMethod(type.GetGenericArguments());
var res = getValFunc.Invoke(null, new object[] { val, unmarshallingErrors });
return (T)res;
}
if (typeof(IDictionary).IsAssignableFrom(type))
{
var getValFunc = GetDictionaryMethod.MakeGenericMethod(type.GetGenericArguments()[1]);
var res = getValFunc.Invoke(null, new object[] { val, unmarshallingErrors });
return (T)res;
}
{
var instance = Activator.CreateInstance<T>();
foreach (var field in type.GetFields())
{
var nonSerializedAttribute = field.GetCustomAttribute<NonSerializedAttribute>();
if (nonSerializedAttribute != null)
{
continue;
}
var mainFieldUnmarshallingErrors = DeserializeToField(instance, field, val, field.Name);
var success = !mainFieldUnmarshallingErrors.Any();
if (!success)
{
var alternativeSerializationFormats =
field.GetCustomAttributes<AlternativeSerializationFormat>();
foreach (var attr in alternativeSerializationFormats)
{
var alternativeField = type.GetField(attr.AlternativeFormatFieldName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var alternativeUnmarshallingErrors = DeserializeToField(instance, alternativeField, val, field.Name);
if (!alternativeUnmarshallingErrors.Any())
{
success = true;
break;
}
}
}
if (!success)
{
unmarshallingErrors.AddRange(mainFieldUnmarshallingErrors);
}
}
return instance;
}
}
private static HashSet<Type> NumericTypes = new HashSet<Type>
{
typeof(int),
typeof(float),
typeof(double),
typeof(decimal),
};
internal static bool IsNumericType(Type type)
{
return NumericTypes.Contains(type);
}
private static List<UnmarshallingException> DeserializeToField<T>(T instance, FieldInfo field, object val, string fieldName)
{
List<UnmarshallingException> unmarshallingErrors = new List<UnmarshallingException>();
var getValFunc = GetValForFieldMethod.MakeGenericMethod(field.FieldType);
if (!(val is Dictionary<string, object>))
{
unmarshallingErrors.Add(new UnmarshallingException(string.Join(".", fieldStack), typeof(Dictionary<string, object>), val.GetType()));
return unmarshallingErrors;
}
var dict = (Dictionary<string, object>)val;
if (dict.TryGetValue(fieldName, out var dictVal))
{
var res = getValFunc.Invoke(null, new object[] { dictVal, fieldName, unmarshallingErrors });
field.SetValue(instance, res);
}
return unmarshallingErrors;
}
private static List<T> GetList<T>(object val, ref List<UnmarshallingException> unmarshallingErrors)
{
var list = new List<T>();
if (!(val is List<object>))
{
unmarshallingErrors.Add(new UnmarshallingException(string.Join(".", fieldStack), typeof(List<T>), val.GetType()));
return null;
}
var i = 0;
foreach (var item in (List<object>)val)
{
list.Add(GetValueForFieldInternal<T>(item, $@"[{i}]", ref unmarshallingErrors));
++i;
}
return list;
}
private static Dictionary<string, T> GetDictionary<T>(object val, ref List<UnmarshallingException> unmarshallingErrors)
{
var result = new Dictionary<string, T>();
if (!(val is Dictionary<string, object>))
{
unmarshallingErrors.Add(new UnmarshallingException(string.Join(".", fieldStack), typeof(Dictionary<string, object>), val.GetType()));
return null;
}
foreach (var item in (Dictionary<string, object>)val)
{
result.Add(item.Key, GetValueForFieldInternal<T>(item.Value, $@"[{item.Key}]", ref unmarshallingErrors));
}
return result;
}
}
internal class AlternativeSerializationFormat : Attribute
{
public string AlternativeFormatFieldName { get; }
internal AlternativeSerializationFormat(string alternativeFormatFieldName)
{
AlternativeFormatFieldName = alternativeFormatFieldName;
}
}
internal class UnmarshallingException : Exception
{
private readonly string jsonFieldName;
private readonly Type expectedType;
private readonly Type actualType;
public UnmarshallingException(string jsonFieldName, Type expectedType, Type actualType)
{
this.jsonFieldName = jsonFieldName;
this.expectedType = expectedType;
this.actualType = actualType;
}
public override string Message => $@"Error marshalling '{jsonFieldName}' expected {expectedType.Name} but got {actualType} instead";
}
}