diff --git a/Streamistry.Core/Pipes/Mappers/Plucker.cs b/Streamistry.Core/Pipes/Mappers/Plucker.cs index 972ae7a..9dc0ef8 100644 --- a/Streamistry.Core/Pipes/Mappers/Plucker.cs +++ b/Streamistry.Core/Pipes/Mappers/Plucker.cs @@ -15,22 +15,40 @@ public Plucker(IChainablePipe upstream, Expression> lambda) { - var propInfo = GetPropertyInfo(lambda); - return (TOutput?)propInfo.GetValue(input, null); + if (input is null) + return default; + + return (TOutput?)GetNestedPropertyValue(input, lambda); } - private static PropertyInfo GetPropertyInfo(Expression> lambda) + public static object? GetNestedPropertyValue(object target, LambdaExpression lambda) { - if (lambda.Body is not MemberExpression member) + MemberExpression? expr; + if (lambda.Body is not MemberExpression) throw new ArgumentException($"Expression '{lambda}' refers to a method, not a property."); - if (member.Member is not PropertyInfo propInfo) - throw new ArgumentException($"Expression '{lambda}' refers to a field, not a property."); + if (lambda.Body is UnaryExpression unaryExpression) + expr = unaryExpression.Operand as MemberExpression ?? throw new InvalidOperationException(); + else + expr = lambda.Body as MemberExpression; + + var members = new Stack(); - var type = typeof(TInput); - if (propInfo.ReflectedType != null && type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType)) - throw new ArgumentException($"Expression '{lambda}' refers to a property that is not a property from type {type}."); + while (expr != null) + { + members.Push(expr); + expr = expr.Expression as MemberExpression; + } - return propInfo; + // Now we evaluate them from root to leaf + object? item = target; + while (members.Count > 0) + { + var memberExpression = members.Pop(); + var propertyInfo = (memberExpression.Member as PropertyInfo) + ?? throw new ArgumentException("Member is not a property"); + item = propertyInfo.GetValue(item); + } + return item; } } diff --git a/Streamistry.Testing/Pipes/Mappers/PluckerTests.cs b/Streamistry.Testing/Pipes/Mappers/PluckerTests.cs index e358942..8297d33 100644 --- a/Streamistry.Testing/Pipes/Mappers/PluckerTests.cs +++ b/Streamistry.Testing/Pipes/Mappers/PluckerTests.cs @@ -23,4 +23,15 @@ public void Emit_HumanPluckerOnBirthDay_BirthDay() pipeline.Emit(new Human("Albert Einstein", new DateOnly(1879, 3, 14))); Assert.That(sink.State.Last(), Is.EqualTo(new DateOnly(1879, 3, 14))); } + + [Test] + public void Emit_HumanPluckerOnBirthMonth_Integer() + { + var pipeline = new Pipeline(); + var plucker = new Plucker(pipeline, h => h.BirthDay.Month); + var sink = new MemorySink(plucker); + + pipeline.Emit(new Human("Albert Einstein", new DateOnly(1879, 3, 14))); + Assert.That(sink.State.Last(), Is.EqualTo(3)); + } }