From 8dcf2c631b92aeb9bdbeb5bdc9141fd4073cd700 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Wed, 27 May 2020 13:31:44 +1000 Subject: [PATCH] Add special serialization handling for nullable ValueTuples (#4713) This commit adds special serialization handling for nullable ValueTuples. Specialized handling is required as without, a nullable ValueTuple type ends up falling into CustomDyanmicObjectResolver inside of NestFormatterResolver, which will build a dynamic formatter using MetaType et.al, which enumerates interfaces and can cause a CLR error when calling `at System.Runtime.CompilerServices.ITuple.get_Length()`. Fixes #4703 --- .../Resolvers/DynamicGenericResolver.cs | 39 +++++++++++++++++++ tests/Tests.Reproduce/GithubIssue4703.cs | 38 ++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/Tests.Reproduce/GithubIssue4703.cs diff --git a/src/Elasticsearch.Net/Utf8Json/Resolvers/DynamicGenericResolver.cs b/src/Elasticsearch.Net/Utf8Json/Resolvers/DynamicGenericResolver.cs index ae4b6f261ad..c5a824bc9af 100644 --- a/src/Elasticsearch.Net/Utf8Json/Resolvers/DynamicGenericResolver.cs +++ b/src/Elasticsearch.Net/Utf8Json/Resolvers/DynamicGenericResolver.cs @@ -229,6 +229,45 @@ internal static object GetFormatter(Type t) return CreateInstance(tupleFormatterType, ti.GenericTypeArguments); } + + // Nullable ValueTuple + else if (isNullable && nullableElementType.GetTypeInfo().IsConstructedGenericType() && + nullableElementType.GetTypeInfo().FullName.StartsWith("System.ValueTuple")) + { + Type tupleFormatterType = null; + switch (nullableElementType.GenericTypeArguments.Length) + { + case 1: + tupleFormatterType = typeof(ValueTupleFormatter<>); + break; + case 2: + tupleFormatterType = typeof(ValueTupleFormatter<,>); + break; + case 3: + tupleFormatterType = typeof(ValueTupleFormatter<,,>); + break; + case 4: + tupleFormatterType = typeof(ValueTupleFormatter<,,,>); + break; + case 5: + tupleFormatterType = typeof(ValueTupleFormatter<,,,,>); + break; + case 6: + tupleFormatterType = typeof(ValueTupleFormatter<,,,,,>); + break; + case 7: + tupleFormatterType = typeof(ValueTupleFormatter<,,,,,,>); + break; + case 8: + tupleFormatterType = typeof(ValueTupleFormatter<,,,,,,,>); + break; + default: + break; + } + + var tupleFormatter = CreateInstance(tupleFormatterType, nullableElementType.GenericTypeArguments); + return CreateInstance(typeof(StaticNullableFormatter<>), new [] { nullableElementType }, tupleFormatter); + } #endif // ArraySegement diff --git a/tests/Tests.Reproduce/GithubIssue4703.cs b/tests/Tests.Reproduce/GithubIssue4703.cs new file mode 100644 index 00000000000..501e49e3539 --- /dev/null +++ b/tests/Tests.Reproduce/GithubIssue4703.cs @@ -0,0 +1,38 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Text; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using FluentAssertions; +using Nest; +using Tests.Core.Client; + +namespace Tests.Reproduce +{ + public class GithubIssue4703 + { + [U] + public void NullableValueTupleDoesNotThrow() + { + Func action = () => + TestClient.DefaultInMemoryClient.Index( + new ExampleDoc + { + tupleNullable = ("somestring", 42), + }, i => i.Index("index")); + + var a = action.Should().NotThrow(); + var response = a.Subject; + + var json = Encoding.UTF8.GetString(response.ApiCall.RequestBodyInBytes); + json.Should().Be(@"{""tupleNullable"":{""Item1"":""somestring"",""Item2"":42}}"); + } + } + + public class ExampleDoc + { + public (string info, int number)? tupleNullable { get; set; } + } +}