Skip to content

Commit 2af48ca

Browse files
Add special serialization handling for nullable ValueTuples (#4713) (#4715)
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 Co-authored-by: Russ Cam <russ.cam@elastic.co>
1 parent ddc2772 commit 2af48ca

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

src/Elasticsearch.Net/Utf8Json/Resolvers/DynamicGenericResolver.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,45 @@ internal static object GetFormatter(Type t)
229229

230230
return CreateInstance(tupleFormatterType, ti.GenericTypeArguments);
231231
}
232+
233+
// Nullable ValueTuple
234+
else if (isNullable && nullableElementType.GetTypeInfo().IsConstructedGenericType() &&
235+
nullableElementType.GetTypeInfo().FullName.StartsWith("System.ValueTuple"))
236+
{
237+
Type tupleFormatterType = null;
238+
switch (nullableElementType.GenericTypeArguments.Length)
239+
{
240+
case 1:
241+
tupleFormatterType = typeof(ValueTupleFormatter<>);
242+
break;
243+
case 2:
244+
tupleFormatterType = typeof(ValueTupleFormatter<,>);
245+
break;
246+
case 3:
247+
tupleFormatterType = typeof(ValueTupleFormatter<,,>);
248+
break;
249+
case 4:
250+
tupleFormatterType = typeof(ValueTupleFormatter<,,,>);
251+
break;
252+
case 5:
253+
tupleFormatterType = typeof(ValueTupleFormatter<,,,,>);
254+
break;
255+
case 6:
256+
tupleFormatterType = typeof(ValueTupleFormatter<,,,,,>);
257+
break;
258+
case 7:
259+
tupleFormatterType = typeof(ValueTupleFormatter<,,,,,,>);
260+
break;
261+
case 8:
262+
tupleFormatterType = typeof(ValueTupleFormatter<,,,,,,,>);
263+
break;
264+
default:
265+
break;
266+
}
267+
268+
var tupleFormatter = CreateInstance(tupleFormatterType, nullableElementType.GenericTypeArguments);
269+
return CreateInstance(typeof(StaticNullableFormatter<>), new [] { nullableElementType }, tupleFormatter);
270+
}
232271
#endif
233272

234273
// ArraySegement
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
using System.Text;
7+
using Elastic.Elasticsearch.Xunit.XunitPlumbing;
8+
using FluentAssertions;
9+
using Nest;
10+
using Tests.Core.Client;
11+
12+
namespace Tests.Reproduce
13+
{
14+
public class GithubIssue4703
15+
{
16+
[U]
17+
public void NullableValueTupleDoesNotThrow()
18+
{
19+
Func<IndexResponse> action = () =>
20+
TestClient.DefaultInMemoryClient.Index(
21+
new ExampleDoc
22+
{
23+
tupleNullable = ("somestring", 42),
24+
}, i => i.Index("index"));
25+
26+
var a = action.Should().NotThrow();
27+
var response = a.Subject;
28+
29+
var json = Encoding.UTF8.GetString(response.ApiCall.RequestBodyInBytes);
30+
json.Should().Be(@"{""tupleNullable"":{""Item1"":""somestring"",""Item2"":42}}");
31+
}
32+
}
33+
34+
public class ExampleDoc
35+
{
36+
public (string info, int number)? tupleNullable { get; set; }
37+
}
38+
}

0 commit comments

Comments
 (0)