protected static TDelegate BuildDeserializeDelegateExpressions <TDelegate, TReturn>(ParameterExpression inputExpression, Expression nameSpanExpression) { var nameSpan = Expression.Variable(typeof(ReadOnlySpan <TSymbol>), "nameSpan"); var returnValue = Expression.Variable(typeof(TReturn), "returnValue"); var lengthParameter = Expression.Variable(typeof(int), "length"); var endOfBlockLabel = Expression.Label(); var assignNameSpan = Expression.Assign(nameSpan, nameSpanExpression); var lengthExpression = Expression.Assign(lengthParameter, Expression.PropertyOrField(nameSpan, "Length")); var byteNameSpan = Expression.Variable(typeof(ReadOnlySpan <byte>), "byteNameSpan"); var parameters = new List <ParameterExpression> { nameSpan, lengthParameter, returnValue }; if (typeof(TSymbol) == typeof(char)) { var asBytesMethodInfo = FindGenericMethod(typeof(MemoryMarshal), nameof(MemoryMarshal.AsBytes), BindingFlags.Public | BindingFlags.Static, typeof(char), typeof(ReadOnlySpan <>)); nameSpanExpression = Expression.Call(null, asBytesMethodInfo, assignNameSpan); assignNameSpan = Expression.Assign(byteNameSpan, nameSpanExpression); parameters.Add(byteNameSpan); } else { byteNameSpan = nameSpan; } var memberInfos = new List <JsonMemberInfo>(); var dict = new Dictionary <string, TReturn>(); foreach (var name in Enum.GetNames(typeof(T))) { var formattedValue = GetFormattedValue(name); memberInfos.Add(new JsonMemberInfo(name, typeof(T), null, formattedValue, false, true, false, null, null)); var value = Enum.Parse(typeof(T), name); dict.Add(name, (TReturn)Convert.ChangeType(value, typeof(TReturn))); } Expression MatchExpressionFunctor(JsonMemberInfo memberInfo) { var enumValue = dict[memberInfo.MemberName]; return(Expression.Assign(returnValue, Expression.Constant(enumValue))); } var returnTarget = Expression.Label(returnValue.Type); var returnLabel = Expression.Label(returnTarget, returnValue); var expressions = new List <Expression> { assignNameSpan, lengthExpression, MemberComparisonBuilder.Build <TSymbol>(memberInfos, 0, lengthParameter, byteNameSpan, endOfBlockLabel, MatchExpressionFunctor), Expression.Throw(Expression.Constant(new InvalidOperationException())), Expression.Label(endOfBlockLabel), returnLabel }; var blockExpression = Expression.Block(parameters, expressions); var lambdaExpression = Expression.Lambda <TDelegate>(blockExpression, inputExpression); return(lambdaExpression.Compile()); }
/// <summary> /// Creates the deserializer for both utf8 and utf16 /// There should not be a large difference between utf8 and utf16 besides member names /// </summary> protected static DeserializeDelegate <T, TSymbol> BuildDeserializeDelegate <T, TSymbol, TResolver>() where TResolver : IJsonFormatterResolver <TSymbol, TResolver>, new() where TSymbol : struct { var resolver = StandardResolvers.GetResolver <TSymbol, TResolver>(); var objectDescription = resolver.GetObjectDescription <T>(); var memberInfos = objectDescription.Where(a => a.CanWrite).ToList(); var readerParameter = Expression.Parameter(typeof(JsonReader <TSymbol>).MakeByRefType(), "reader"); // can't deserialize abstract and only support interfaces based on IEnumerable<T> (this includes, IList, IReadOnlyList, IDictionary et al.) foreach (var memberInfo in memberInfos) { var memberType = memberInfo.MemberType; if (memberType.IsAbstract) { if (memberType.TryGetTypeOfGenericInterface(typeof(IEnumerable <>), out _)) { continue; } return(Expression .Lambda <DeserializeDelegate <T, TSymbol> >(Expression.Block( Expression.Throw(Expression.Constant(new NotSupportedException($"{typeof(T).Name} contains abstract members."))), Expression.Default(typeof(T))), readerParameter).Compile()); } } if (typeof(T).IsAbstract) { return(Expression.Lambda <DeserializeDelegate <T, TSymbol> >(Expression.Default(typeof(T)), readerParameter).Compile()); } if (memberInfos.Count == 0) { Expression createExpression = null; if (typeof(T).IsClass) { var ci = typeof(T).GetConstructor(Type.EmptyTypes); if (ci != null) { createExpression = Expression.New(ci); } } if (createExpression == null) { createExpression = Expression.Default(typeof(T)); } return(Expression.Lambda <DeserializeDelegate <T, TSymbol> >(createExpression, readerParameter).Compile()); } var returnValue = Expression.Variable(typeof(T), "result"); MethodInfo nameSpanMethodInfo; MethodInfo tryReadEndObjectMethodInfo; MethodInfo beginObjectOrThrowMethodInfo; if (typeof(TSymbol) == typeof(char)) { nameSpanMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .ReadUtf16NameSpan)); tryReadEndObjectMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .TryReadUtf16IsEndObjectOrValueSeparator)); beginObjectOrThrowMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .ReadUtf16BeginObjectOrThrow)); } else if (typeof(TSymbol) == typeof(byte)) { nameSpanMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .ReadUtf8NameSpan)); tryReadEndObjectMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .TryReadUtf8IsEndObjectOrValueSeparator)); beginObjectOrThrowMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .ReadUtf8BeginObjectOrThrow)); } else { throw new NotSupportedException(); } // We need to decide during generation if we handle constructors or normal member assignment, the difference is done in the functor below Func <JsonMemberInfo, Expression> matchExpressionFunctor; Expression[] constructorParameterExpresssions = null; if (objectDescription.Constructor != null) { // If we want to use the constructor we serialize into an array of variables internally and then create the object from that var dict = objectDescription.ConstructorMapping; constructorParameterExpresssions = new Expression[dict.Count]; foreach (var valueTuple in dict) { constructorParameterExpresssions[valueTuple.Value.Index] = Expression.Variable(valueTuple.Value.Type); } matchExpressionFunctor = memberInfo => { var element = dict[memberInfo.MemberName]; var formatter = resolver.GetFormatter(memberInfo); var formatterType = formatter.GetType(); var fieldInfo = formatterType.GetField("Default", BindingFlags.Static | BindingFlags.Public); return(Expression.Assign(constructorParameterExpresssions[element.Index], Expression.Call(Expression.Field(null, fieldInfo), FindPublicInstanceMethod(formatterType, "Deserialize", readerParameter.Type.MakeByRefType()), readerParameter))); }; } else { // The normal assign to member type matchExpressionFunctor = memberInfo => { var formatter = resolver.GetFormatter(memberInfo); var formatterType = formatter.GetType(); var fieldInfo = formatterType.GetField("Default", BindingFlags.Static | BindingFlags.Public); return(Expression.Assign(Expression.PropertyOrField(returnValue, memberInfo.MemberName), Expression.Call(Expression.Field(null, fieldInfo), FindPublicInstanceMethod(formatterType, "Deserialize", readerParameter.Type.MakeByRefType()), readerParameter))); }; } var nameSpan = Expression.Variable(typeof(ReadOnlySpan <TSymbol>), "nameSpan"); var lengthParameter = Expression.Variable(typeof(int), "length"); var endOfBlockLabel = Expression.Label(); var nameSpanExpression = Expression.Call(readerParameter, nameSpanMethodInfo); var assignNameSpan = Expression.Assign(nameSpan, nameSpanExpression); var lengthExpression = Expression.Assign(lengthParameter, Expression.PropertyOrField(nameSpan, "Length")); var byteNameSpan = Expression.Variable(typeof(ReadOnlySpan <byte>), "byteNameSpan"); var parameters = new List <ParameterExpression> { nameSpan, lengthParameter }; if (typeof(TSymbol) == typeof(char)) { // For utf16 we need to convert the attribute name to bytes to feed it to the matching logic Expression <Action> functor = () => MemoryMarshal.AsBytes(new ReadOnlySpan <char>()); var asBytesMethodInfo = (functor.Body as MethodCallExpression).Method; nameSpanExpression = Expression.Call(null, asBytesMethodInfo, assignNameSpan); assignNameSpan = Expression.Assign(byteNameSpan, nameSpanExpression); parameters.Add(byteNameSpan); } else { byteNameSpan = nameSpan; } MethodInfo skipNextMethodInfo; if (typeof(TSymbol) == typeof(char)) { skipNextMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .SkipNextUtf16Segment)); } else if (typeof(TSymbol) == typeof(byte)) { skipNextMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .SkipNextUtf8Segment)); } else { throw new NotSupportedException(); } var expressions = new List <Expression> { assignNameSpan, lengthExpression, MemberComparisonBuilder.Build <TSymbol>(memberInfos, 0, lengthParameter, byteNameSpan, endOfBlockLabel, matchExpressionFunctor), Expression.Call(readerParameter, skipNextMethodInfo), Expression.Label(endOfBlockLabel), }; var ifBlock = Expression.Block(parameters, expressions); var countExpression = Expression.Parameter(typeof(int), "count"); var abortExpression = Expression.IsTrue(Expression.Call(readerParameter, tryReadEndObjectMethodInfo, countExpression)); var readBeginObject = Expression.Call(readerParameter, beginObjectOrThrowMethodInfo); var loopAbort = Expression.Label(typeof(void)); var returnTarget = Expression.Label(returnValue.Type); Expression block; if (objectDescription.Constructor != null) { var blockParameters = new List <ParameterExpression> { returnValue, countExpression }; // ReSharper disable AssignNullToNotNullAttribute blockParameters.AddRange(constructorParameterExpresssions.OfType <ParameterExpression>()); // ReSharper restore AssignNullToNotNullAttribute block = Expression.Block(blockParameters, readBeginObject, Expression.Loop( Expression.IfThenElse(abortExpression, Expression.Break(loopAbort), ifBlock), loopAbort ), Expression.Assign(returnValue, Expression.New(objectDescription.Constructor, constructorParameterExpresssions)), Expression.Label(returnTarget, returnValue) ); } else { block = Expression.Block(new[] { returnValue, countExpression }, readBeginObject, Expression.Assign(returnValue, Expression.New(returnValue.Type)), Expression.Loop( Expression.IfThenElse(abortExpression, Expression.Break(loopAbort), ifBlock), loopAbort ), Expression.Label(returnTarget, returnValue) ); } var lambda = Expression.Lambda <DeserializeDelegate <T, TSymbol> >(block, readerParameter); return(lambda.Compile()); }
/// <summary> /// Not sure if it's useful to build something like the automaton from the compelxformatter here /// </summary> private static DeserializeDelegate BuildDeserializeDelegate() { var readerParameter = Expression.Parameter(typeof(JsonReader <TSymbol>).MakeByRefType(), "reader"); MethodInfo nameSpanMethodInfo; if (typeof(TSymbol) == typeof(char)) { nameSpanMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .ReadUtf16StringSpan)); } else if (typeof(TSymbol) == typeof(byte)) { nameSpanMethodInfo = FindPublicInstanceMethod(readerParameter.Type, nameof(JsonReader <TSymbol> .ReadUtf8StringSpan)); } else { throw new NotSupportedException(); } var returnValue = Expression.Variable(typeof(T), "returnValue"); var nameSpan = Expression.Variable(typeof(ReadOnlySpan <TSymbol>), "nameSpan"); var lengthParameter = Expression.Variable(typeof(int), "length"); var endOfBlockLabel = Expression.Label(); var nameSpanExpression = Expression.Call(readerParameter, nameSpanMethodInfo); var assignNameSpan = Expression.Assign(nameSpan, nameSpanExpression); var lengthExpression = Expression.Assign(lengthParameter, Expression.PropertyOrField(nameSpan, "Length")); var byteNameSpan = Expression.Variable(typeof(ReadOnlySpan <byte>), "byteNameSpan"); var parameters = new List <ParameterExpression> { nameSpan, lengthParameter, returnValue }; if (typeof(TSymbol) == typeof(char)) { Expression <Action> functor = () => MemoryMarshal.AsBytes(new ReadOnlySpan <char>()); var asBytesMethodInfo = (functor.Body as MethodCallExpression).Method; nameSpanExpression = Expression.Call(null, asBytesMethodInfo, assignNameSpan); assignNameSpan = Expression.Assign(byteNameSpan, nameSpanExpression); parameters.Add(byteNameSpan); } else { byteNameSpan = nameSpan; } var memberInfos = new List <JsonMemberInfo>(); var dict = new Dictionary <string, T>(); foreach (var value in Enum.GetValues(typeof(T))) { var formattedValue = GetFormattedValue(value); memberInfos.Add(new JsonMemberInfo(value.ToString(), typeof(T), null, formattedValue, false, true, false, null)); dict.Add(value.ToString(), (T)value); } Expression MatchExpressionFunctor(JsonMemberInfo memberInfo) { var enumValue = dict[memberInfo.MemberName]; return(Expression.Assign(returnValue, Expression.Constant(enumValue))); } var returnTarget = Expression.Label(returnValue.Type); var returnLabel = Expression.Label(returnTarget, returnValue); var expressions = new List <Expression> { assignNameSpan, lengthExpression, MemberComparisonBuilder.Build <TSymbol>(memberInfos, 0, lengthParameter, byteNameSpan, endOfBlockLabel, MatchExpressionFunctor), Expression.Throw(Expression.Constant(new InvalidOperationException())), Expression.Label(endOfBlockLabel), returnLabel }; var blockExpression = Expression.Block(parameters, expressions); var lambdaExpression = Expression.Lambda <DeserializeDelegate>(blockExpression, readerParameter); return(lambdaExpression.Compile()); }