using System.Linq.Expressions; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; namespace AyCode.Blazor.Components.Services.ExpressionHelpers; /// /// Deserializes AcExpressionNode DTO back to Expression tree. /// public class AcExpressionDeserializer { private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { new JsonStringEnumConverter() } }; private readonly Dictionary _parameters = new(); /// /// Deserializes JSON to Expression. /// public static Expression ExpressionFromJson(string json, Type? entityType = null) { var node = JsonSerializer.Deserialize(json, JsonOptions) ?? throw new ArgumentException("Invalid expression JSON", nameof(json)); var deserializer = new AcExpressionDeserializer(); return deserializer.Deserialize(node, entityType); } /// /// Deserializes JSON to typed Expression. /// public static Expression> ExpressionFromJson(string json) { var expression = ExpressionFromJson(json, typeof(T)); return (Expression>)expression; } /// /// Deserializes AcExpressionNode to Expression. /// 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(); 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 }