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; } + } +}