AyCode.Blazor/AyCode.Blazor.Components/Services/ExpressionHelpers/AcExpressionDeserializer.cs

357 lines
15 KiB
C#

using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AyCode.Blazor.Components.Services.ExpressionHelpers;
/// <summary>
/// Deserializes AcExpressionNode DTO back to Expression tree.
/// </summary>
public class AcExpressionDeserializer
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
};
private readonly Dictionary<int, ParameterExpression> _parameters = new();
/// <summary>
/// Deserializes JSON to Expression.
/// </summary>
public static Expression ExpressionFromJson(string json, Type? entityType = null)
{
var node = JsonSerializer.Deserialize<AcExpressionNode>(json, JsonOptions)
?? throw new ArgumentException("Invalid expression JSON", nameof(json));
var deserializer = new AcExpressionDeserializer();
return deserializer.Deserialize(node, entityType);
}
/// <summary>
/// Deserializes JSON to typed Expression.
/// </summary>
public static Expression<Func<T, TResult>> ExpressionFromJson<T, TResult>(string json)
{
var expression = ExpressionFromJson(json, typeof(T));
return (Expression<Func<T, TResult>>)expression;
}
/// <summary>
/// Deserializes AcExpressionNode to Expression.
/// </summary>
public Expression Deserialize(AcExpressionNode node, Type? entityType = null)
{
return node.NodeType switch
{
ExpressionType.Lambda => DeserializeLambda(node, entityType),
ExpressionType.Parameter => DeserializeParameter(node),
ExpressionType.Constant => DeserializeConstant(node),
ExpressionType.MemberAccess => DeserializeMemberAccess(node, entityType),
ExpressionType.Call => DeserializeMethodCall(node, entityType),
ExpressionType.Conditional => DeserializeConditional(node, entityType),
ExpressionType.New => DeserializeNew(node, entityType),
ExpressionType.MemberInit => DeserializeMemberInit(node, entityType),
ExpressionType.NewArrayInit or ExpressionType.NewArrayBounds => DeserializeNewArray(node, entityType),
ExpressionType.Invoke => DeserializeInvocation(node, entityType),
ExpressionType.TypeIs or ExpressionType.TypeAs => DeserializeTypeBinary(node, entityType),
// Unary expressions
ExpressionType.Not or ExpressionType.Negate or ExpressionType.NegateChecked or
ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.ArrayLength or
ExpressionType.Quote or ExpressionType.UnaryPlus
=> DeserializeUnary(node, entityType),
// Binary expressions
ExpressionType.Add or ExpressionType.AddChecked or ExpressionType.Subtract or
ExpressionType.SubtractChecked or ExpressionType.Multiply or ExpressionType.MultiplyChecked or
ExpressionType.Divide or ExpressionType.Modulo or ExpressionType.Power or
ExpressionType.And or ExpressionType.AndAlso or ExpressionType.Or or ExpressionType.OrElse or
ExpressionType.ExclusiveOr or ExpressionType.Equal or ExpressionType.NotEqual or
ExpressionType.LessThan or ExpressionType.LessThanOrEqual or
ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual or
ExpressionType.Coalesce or ExpressionType.ArrayIndex or
ExpressionType.LeftShift or ExpressionType.RightShift
=> DeserializeBinary(node, entityType),
_ => throw new NotSupportedException($"Expression type '{node.NodeType}' is not supported.")
};
}
#region Deserialize Methods
private LambdaExpression DeserializeLambda(AcExpressionNode node, Type? entityType)
{
// Create parameters
var parameters = new List<ParameterExpression>();
if (node.Parameters != null)
{
foreach (var paramNode in node.Parameters)
{
var paramType = entityType ?? ResolveType(paramNode.TypeName);
var param = Expression.Parameter(paramType, paramNode.Name);
_parameters[paramNode.Index] = param;
parameters.Add(param);
// Use entityType only for first parameter
entityType = null;
}
}
var body = Deserialize(node.Body!, null);
return Expression.Lambda(body, parameters);
}
private ParameterExpression DeserializeParameter(AcExpressionNode node)
{
if (node.ParameterIndex.HasValue && _parameters.TryGetValue(node.ParameterIndex.Value, out var param))
return param;
throw new InvalidOperationException($"Parameter '{node.ParameterName}' not found.");
}
private static ConstantExpression DeserializeConstant(AcExpressionNode node)
{
var type = ResolveType(node.TypeName ?? "System.Object");
if (node.Value == null)
return Expression.Constant(null, type);
var value = JsonSerializer.Deserialize(node.Value, type, JsonOptions);
return Expression.Constant(value, type);
}
private Expression DeserializeMemberAccess(AcExpressionNode node, Type? entityType)
{
if (node.Object == null)
{
// Static member access
var declaringType = ResolveType(node.DeclaringType!);
var member = declaringType.GetMember(node.MemberName!, BindingFlags.Public | BindingFlags.Static).FirstOrDefault()
?? throw new InvalidOperationException($"Static member '{node.MemberName}' not found on type '{declaringType.Name}'.");
return Expression.MakeMemberAccess(null, member);
}
var obj = Deserialize(node.Object, entityType);
var memberInfo = obj.Type.GetMember(node.MemberName!, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase).FirstOrDefault()
?? throw new InvalidOperationException($"Member '{node.MemberName}' not found on type '{obj.Type.Name}'.");
return Expression.MakeMemberAccess(obj, memberInfo);
}
private Expression DeserializeMethodCall(AcExpressionNode node, Type? entityType)
{
var arguments = node.Arguments?.Select(a => Deserialize(a, entityType)).ToArray() ?? [];
var argumentTypes = arguments.Select(a => a.Type).ToArray();
var declaringType = node.DeclaringType != null ? ResolveType(node.DeclaringType) : null;
var instance = node.Object != null ? Deserialize(node.Object, entityType) : null;
MethodInfo? method = null;
if (instance != null)
{
// Instance method
method = FindMethod(instance.Type, node.MethodName!, argumentTypes, isStatic: false);
}
else if (declaringType != null)
{
// Static method (including extension methods)
method = FindMethod(declaringType, node.MethodName!, argumentTypes, isStatic: true);
}
if (method == null)
throw new InvalidOperationException($"Method '{node.MethodName}' not found.");
// Handle generic methods
if (method.IsGenericMethodDefinition && node.GenericArguments?.Count > 0)
{
var genericTypes = node.GenericArguments.Select(ResolveType).ToArray();
method = method.MakeGenericMethod(genericTypes);
}
return instance != null
? Expression.Call(instance, method, arguments)
: Expression.Call(method, arguments);
}
private Expression DeserializeBinary(AcExpressionNode node, Type? entityType)
{
var left = Deserialize(node.Left!, entityType);
var right = Deserialize(node.Right!, entityType);
// Handle type mismatches (e.g., nullable comparisons)
if (left.Type != right.Type)
{
if (Nullable.GetUnderlyingType(left.Type) == right.Type)
right = Expression.Convert(right, left.Type);
else if (Nullable.GetUnderlyingType(right.Type) == left.Type)
left = Expression.Convert(left, right.Type);
}
return Expression.MakeBinary(node.NodeType, left, right);
}
private Expression DeserializeUnary(AcExpressionNode node, Type? entityType)
{
var operand = Deserialize(node.Operand!, entityType);
var type = node.TypeName != null ? ResolveType(node.TypeName) : null;
return node.NodeType switch
{
ExpressionType.Convert or ExpressionType.ConvertChecked when type != null
=> Expression.Convert(operand, type),
_ => Expression.MakeUnary(node.NodeType, operand, type)
};
}
private Expression DeserializeConditional(AcExpressionNode node, Type? entityType)
{
var test = Deserialize(node.Test!, entityType);
var ifTrue = Deserialize(node.IfTrue!, entityType);
var ifFalse = Deserialize(node.IfFalse!, entityType);
return Expression.Condition(test, ifTrue, ifFalse);
}
private Expression DeserializeNew(AcExpressionNode node, Type? entityType)
{
var type = ResolveType(node.TypeName!);
var args = node.ConstructorArguments?.Select(a => Deserialize(a, entityType)).ToArray() ?? [];
var argTypes = args.Select(a => a.Type).ToArray();
var ctor = type.GetConstructor(argTypes)
?? throw new InvalidOperationException($"Constructor not found for type '{type.Name}'.");
return Expression.New(ctor, args);
}
private Expression DeserializeMemberInit(AcExpressionNode node, Type? entityType)
{
var type = ResolveType(node.TypeName!);
var args = node.ConstructorArguments?.Select(a => Deserialize(a, entityType)).ToArray() ?? [];
var argTypes = args.Select(a => a.Type).ToArray();
var ctor = type.GetConstructor(argTypes) ?? type.GetConstructor(Type.EmptyTypes)
?? throw new InvalidOperationException($"Constructor not found for type '{type.Name}'.");
var newExpr = Expression.New(ctor, args);
var bindings = node.MemberBindings?.Select(b => DeserializeMemberBinding(b, type, entityType)).ToList()
?? [];
return Expression.MemberInit(newExpr, bindings);
}
private MemberBinding DeserializeMemberBinding(MemberBindingNode node, Type declaringType, Type? entityType)
{
var member = declaringType.GetMember(node.MemberName, BindingFlags.Public | BindingFlags.Instance).FirstOrDefault()
?? throw new InvalidOperationException($"Member '{node.MemberName}' not found on type '{declaringType.Name}'.");
return node.BindingType switch
{
MemberBindingType.Assignment => Expression.Bind(member, Deserialize(node.Expression!, entityType)),
MemberBindingType.MemberBinding => Expression.MemberBind(member,
node.Bindings?.Select(b => DeserializeMemberBinding(b, GetMemberType(member), entityType)) ?? []),
MemberBindingType.ListBinding => Expression.ListBind(member,
node.Initializers?.Select(args => Expression.ElementInit(
GetAddMethod(GetMemberType(member)),
args.Select(a => Deserialize(a, entityType)))) ?? []),
_ => throw new NotSupportedException($"Binding type '{node.BindingType}' is not supported.")
};
}
private Expression DeserializeNewArray(AcExpressionNode node, Type? entityType)
{
var elementType = ResolveType(node.TypeName!).GetElementType()
?? throw new InvalidOperationException("Cannot determine array element type.");
var elements = node.Elements?.Select(e => Deserialize(e, entityType)).ToArray() ?? [];
return Expression.NewArrayInit(elementType, elements);
}
private Expression DeserializeInvocation(AcExpressionNode node, Type? entityType)
{
var expression = Deserialize(node.Object!, entityType);
var arguments = node.Arguments?.Select(a => Deserialize(a, entityType)).ToArray() ?? [];
return Expression.Invoke(expression, arguments);
}
private Expression DeserializeTypeBinary(AcExpressionNode node, Type? entityType)
{
var expression = Deserialize(node.Operand!, entityType);
var type = ResolveType(node.TypeName!);
return node.NodeType == ExpressionType.TypeIs
? Expression.TypeIs(expression, type)
: Expression.TypeAs(expression, type);
}
#endregion
#region Helper Methods
private static MethodInfo? FindMethod(Type type, string methodName, Type[] argumentTypes, bool isStatic)
{
var bindingFlags = BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance);
// Try exact match first
var method = type.GetMethod(methodName, bindingFlags, null, argumentTypes, null);
if (method != null) return method;
// Try finding by name and parameter count
var candidates = type.GetMethods(bindingFlags)
.Where(m => m.Name == methodName && m.GetParameters().Length == argumentTypes.Length)
.ToList();
return candidates.FirstOrDefault();
}
private static Type GetMemberType(MemberInfo member) => member switch
{
PropertyInfo pi => pi.PropertyType,
FieldInfo fi => fi.FieldType,
_ => throw new InvalidOperationException($"Cannot get type for member '{member.Name}'.")
};
private static MethodInfo GetAddMethod(Type collectionType)
{
return collectionType.GetMethod("Add")
?? throw new InvalidOperationException($"Add method not found on type '{collectionType.Name}'.");
}
private static Type ResolveType(string typeName)
{
var type = typeName switch
{
"System.String" or "string" => typeof(string),
"System.Int32" or "int" => typeof(int),
"System.Int64" or "long" => typeof(long),
"System.Int16" or "short" => typeof(short),
"System.Byte" or "byte" => typeof(byte),
"System.Boolean" or "bool" => typeof(bool),
"System.Double" or "double" => typeof(double),
"System.Single" or "float" => typeof(float),
"System.Decimal" or "decimal" => typeof(decimal),
"System.DateTime" => typeof(DateTime),
"System.DateTimeOffset" => typeof(DateTimeOffset),
"System.DateOnly" => typeof(DateOnly),
"System.TimeOnly" => typeof(TimeOnly),
"System.TimeSpan" => typeof(TimeSpan),
"System.Guid" => typeof(Guid),
"System.Object" or "object" => typeof(object),
_ => Type.GetType(typeName)
};
if (type == null && typeName.Contains("Nullable"))
{
var match = System.Text.RegularExpressions.Regex.Match(typeName, @"System\.Nullable`1\[\[(.+?),");
if (match.Success)
{
var underlyingType = ResolveType(match.Groups[1].Value);
type = typeof(Nullable<>).MakeGenericType(underlyingType);
}
}
return type ?? throw new InvalidOperationException($"Cannot resolve type '{typeName}'.");
}
#endregion
}