Skip to content

Commit 812fe8e

Browse files
committed
Fixes for custom serialization settings
Previously, ModifyJsonSerializationSettings() did not respect properties initialized in a custom serializer's constructor, since it was being called in the base class constructor. This is bad practice (hence the ReSharper warning). This change moves all of the initialization code that was in the ctor to a new method Initialize() which must be called after the serializer is instantiated. This change also fixes another issue where our stateful converters were totally disregarding any registered custom serializer. For this reason, Initialize() also accepts an optional JsonConverter which is used to return a new serializer (custom or default) with the stateful converter applied. Relates #2183 Relates #2182
1 parent 5e8a005 commit 812fe8e

File tree

11 files changed

+110
-47
lines changed

11 files changed

+110
-47
lines changed

src/Nest/CommonAbstractions/ConnectionSettings/ConnectionSettingsBase.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,23 @@ protected ConnectionSettingsBase(IConnectionPool connectionPool, IConnection con
7171
this._defaultIndices = new FluentDictionary<Type, string>();
7272
this._defaultTypeNames = new FluentDictionary<Type, string>();
7373

74+
var serializer = ((IConnectionConfigurationValues)this).Serializer as JsonNetSerializer;
75+
if (serializer != null)
76+
serializer.Initialize();
77+
7478
this._inferrer = new Inferrer(this);
7579
}
7680

7781
/// <summary>
7882
/// The default serializer for requests and responses
7983
/// </summary>
8084
/// <returns></returns>
81-
protected override IElasticsearchSerializer DefaultSerializer(TConnectionSettings settings) => new JsonNetSerializer(settings);
85+
protected override IElasticsearchSerializer DefaultSerializer(TConnectionSettings settings)
86+
{
87+
var serializer = new JsonNetSerializer(settings);
88+
serializer.Initialize();
89+
return serializer;
90+
}
8291

8392
/// <summary>
8493
/// This calls SetDefaultTypenameInferrer with an implementation that will pluralize type names. This used to be the default prior to Nest 0.90
@@ -92,7 +101,7 @@ public TConnectionSettings PluralizeTypeNames()
92101
/// <summary>
93102
/// The default index to use when no index is specified.
94103
/// </summary>
95-
/// <param name="defaultIndex">When null/empty/not set might throw
104+
/// <param name="defaultIndex">When null/empty/not set might throw
96105
/// <see cref="NullReferenceException"/> later on when not specifying index explicitly while indexing.
97106
/// </param>
98107
public TConnectionSettings DefaultIndex(string defaultIndex)
@@ -180,7 +189,7 @@ public TConnectionSettings MapPropertiesFor<TDocument>(Action<PropertyMappingDes
180189
return (TConnectionSettings) this;
181190
}
182191

183-
private void ApplyPropertyMappings<TDocument>(IList<IClrTypePropertyMapping<TDocument>> mappings)
192+
private void ApplyPropertyMappings<TDocument>(IList<IClrTypePropertyMapping<TDocument>> mappings)
184193
where TDocument : class
185194
{
186195
foreach (var mapping in mappings)
@@ -228,11 +237,11 @@ public TConnectionSettings InferMappingFor<TDocument>(Func<ClrTypeMappingDescrip
228237
if (inferMapping.IdProperty != null)
229238
#pragma warning disable CS0618 // Type or member is obsolete but will be private in the future OK to call here
230239
this.MapIdPropertyFor<TDocument>(inferMapping.IdProperty);
231-
#pragma warning restore CS0618
240+
#pragma warning restore CS0618
232241

233242
if (inferMapping.Properties != null)
234243
this.ApplyPropertyMappings<TDocument>(inferMapping.Properties);
235-
244+
236245
return (TConnectionSettings) this;
237246
}
238247

src/Nest/CommonAbstractions/SerializationBehavior/JsonNetSerializer.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,25 @@ public class JsonNetSerializer : IElasticsearchSerializer
1616
private static readonly Encoding ExpectedEncoding = new UTF8Encoding(false);
1717

1818
protected IConnectionSettingsValues Settings { get; }
19-
protected ElasticContractResolver ContractResolver { get; }
19+
protected ElasticContractResolver ContractResolver { get; private set; }
2020

2121
//todo this internal smells
2222
internal JsonSerializer Serializer => _defaultSerializer;
2323

24-
private readonly Dictionary<SerializationFormatting, JsonSerializer> _defaultSerializers;
25-
private readonly JsonSerializer _defaultSerializer;
24+
private Dictionary<SerializationFormatting, JsonSerializer> _defaultSerializers;
25+
private JsonSerializer _defaultSerializer;
2626

2727
protected virtual void ModifyJsonSerializerSettings(JsonSerializerSettings settings) { }
2828
protected virtual IList<Func<Type, JsonConverter>> ContractConverters => null;
2929

30-
public JsonNetSerializer(IConnectionSettingsValues settings) : this(settings, null) { }
31-
32-
/// <summary>
33-
/// this constructor is only here for stateful (de)serialization
34-
/// </summary>
35-
internal JsonNetSerializer(IConnectionSettingsValues settings, JsonConverter stateFullConverter)
30+
public JsonNetSerializer(IConnectionSettingsValues settings)
3631
{
3732
this.Settings = settings;
38-
var piggyBackState = stateFullConverter == null ? null : new JsonConverterPiggyBackState { ActualJsonConverter = stateFullConverter };
39-
// ReSharper disable once VirtualMemberCallInContructor
33+
}
34+
35+
public void Initialize(JsonConverter statefulConverter = null)
36+
{
37+
var piggyBackState = statefulConverter == null ? null : new JsonConverterPiggyBackState { ActualJsonConverter = statefulConverter };
4038
this.ContractResolver = new ElasticContractResolver(this.Settings, this.ContractConverters) { PiggyBackState = piggyBackState };
4139

4240
this._defaultSerializer = JsonSerializer.Create(this.CreateSettings(SerializationFormatting.None));

src/Nest/Document/Multiple/MultiGet/ElasticClient-MultiGet.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
using System;
22
using System.IO;
3+
using System.Threading;
34
using System.Threading.Tasks;
45
using Elasticsearch.Net;
56
using Newtonsoft.Json;
67

78
namespace Nest
89
{
9-
using System.Threading;
1010
using MultiGetConverter = Func<IApiCallDetails, Stream, MultiGetResponse>;
1111

1212
public partial interface IElasticClient
@@ -40,7 +40,7 @@ public IMultiGetResponse MultiGet(Func<MultiGetDescriptor, IMultiGetRequest> sel
4040
public IMultiGetResponse MultiGet(IMultiGetRequest request) =>
4141
this.Dispatcher.Dispatch<IMultiGetRequest, MultiGetRequestParameters, MultiGetResponse>(
4242
request,
43-
new MultiGetConverter((r, s) => this.DeserializeMultiGetResponse(r, s, CreateCovariantMultiGetConverter(request))),
43+
new MultiGetConverter((r, s) => this.DeserializeMultiGetResponse(r, s, new MultiGetHitJsonConverter(request))),
4444
this.LowLevelDispatch.MgetDispatch<MultiGetResponse>
4545
);
4646

@@ -53,13 +53,15 @@ public IMultiGetResponse MultiGet(IMultiGetRequest request) =>
5353
this.Dispatcher.DispatchAsync<IMultiGetRequest, MultiGetRequestParameters, MultiGetResponse, IMultiGetResponse>(
5454
request,
5555
cancellationToken,
56-
new MultiGetConverter((r, s) => this.DeserializeMultiGetResponse(r, s, CreateCovariantMultiGetConverter(request))),
56+
new MultiGetConverter((r, s) => this.DeserializeMultiGetResponse(r, s, new MultiGetHitJsonConverter(request))),
5757
this.LowLevelDispatch.MgetDispatchAsync<MultiGetResponse>
5858
);
59-
private MultiGetResponse DeserializeMultiGetResponse(IApiCallDetails response, Stream stream, JsonConverter converter)=>
60-
new JsonNetSerializer(this.ConnectionSettings, converter).Deserialize<MultiGetResponse>(stream);
61-
62-
private JsonConverter CreateCovariantMultiGetConverter(IMultiGetRequest descriptor) => new MultiGetHitJsonConverter(descriptor);
6359

60+
private MultiGetResponse DeserializeMultiGetResponse(IApiCallDetails response, Stream stream, JsonConverter converter)
61+
{
62+
var serializer = this.Serializer as JsonNetSerializer ?? new JsonNetSerializer(this.ConnectionSettings);
63+
serializer.Initialize(converter);
64+
return serializer.Deserialize<MultiGetResponse>(stream);
65+
}
6466
}
6567
}

src/Nest/Search/MultiSearch/ElasticClient-MultiSearch.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public IMultiSearchResponse MultiSearch(IMultiSearchRequest request)
4242
(p, d) =>
4343
{
4444
var converter = CreateMultiSearchDeserializer(p);
45-
var serializer = new JsonNetSerializer(this.ConnectionSettings, converter);
45+
var serializer = this.Serializer as JsonNetSerializer ?? new JsonNetSerializer(this.ConnectionSettings);
46+
serializer.Initialize(converter);
4647
var json = serializer.SerializeToBytes(p).Utf8String();
4748
var creator = new MultiSearchCreator((r, s) => serializer.Deserialize<MultiSearchResponse>(s));
4849
request.RequestParameters.DeserializationOverride(creator);
@@ -65,7 +66,8 @@ public IMultiSearchResponse MultiSearch(IMultiSearchRequest request)
6566
(p, d, c) =>
6667
{
6768
var converter = CreateMultiSearchDeserializer(p);
68-
var serializer = new JsonNetSerializer(this.ConnectionSettings, converter);
69+
var serializer = this.Serializer as JsonNetSerializer ?? new JsonNetSerializer(this.ConnectionSettings);
70+
serializer.Initialize(converter);
6971
var json = serializer.SerializeToBytes(p).Utf8String();
7072
var creator = new MultiSearchCreator((r, s) => serializer.Deserialize<MultiSearchResponse>(s));
7173
request.RequestParameters.DeserializationOverride(creator);

src/Nest/Search/MultiSearch/MultiSearchResponseJsonConverter.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,24 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
5454
var descriptor = m.Descriptor.Value;
5555
var concreteTypeSelector = descriptor.TypeSelector;
5656
var baseType = m.Descriptor.Value.ClrType ?? typeof(object);
57-
57+
5858
var generic = MakeDelegateMethodInfo.MakeGenericMethod(baseType);
5959

6060
if (concreteTypeSelector != null)
6161
{
6262
var state = typeof(ConcreteTypeConverter<>).CreateGenericInstance(baseType, concreteTypeSelector) as JsonConverter;
6363
if (state != null)
6464
{
65-
var elasticSerializer = new JsonNetSerializer(this._settings, state);
65+
var elasticSerializer = new JsonNetSerializer(this._settings);
66+
elasticSerializer.Initialize(state);
6667

6768
generic.Invoke(null, new object[] { m, elasticSerializer.Serializer, response.Responses, this._settings });
6869
continue;
6970
}
7071
}
7172
generic.Invoke(null, new object[] { m, serializer, response.Responses, this._settings });
7273
}
73-
74+
7475
return response;
7576
}
7677

@@ -86,9 +87,9 @@ private class MultiHitTuple
8687
}
8788

8889
private static void CreateMultiHit<T>(
89-
MultiHitTuple tuple,
90-
JsonSerializer serializer,
91-
IDictionary<string, object> collection,
90+
MultiHitTuple tuple,
91+
JsonSerializer serializer,
92+
IDictionary<string, object> collection,
9293
IConnectionSettingsValues settings
9394
)
9495
where T : class

src/Nest/Search/Search/ElasticClient-ResponseCovariance.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public partial class ElasticClient
1010
private TRequest CovariantConverterWhenNeeded<T, TResult, TRequest, TRequestParameters>(RouteValues p, TRequest d)
1111
where T : class
1212
where TResult : class
13-
where TRequest : IRequest<TRequestParameters>, ICovariantSearchRequest
13+
where TRequest : IRequest<TRequestParameters>, ICovariantSearchRequest
1414
where TRequestParameters : IRequestParameters, new()
1515
{
1616
d.RequestParameters.DeserializationOverride = this.CreateSearchDeserializer<T, TResult, TRequest, TRequestParameters>(d);;
@@ -20,7 +20,7 @@ private TRequest CovariantConverterWhenNeeded<T, TResult, TRequest, TRequestPara
2020
private Func<IApiCallDetails, Stream, SearchResponse<TResult>> CreateSearchDeserializer<T, TResult, TRequest, TRequestParameters>(TRequest request)
2121
where T : class
2222
where TResult : class
23-
where TRequest : IRequest<TRequestParameters>, ICovariantSearchRequest
23+
where TRequest : IRequest<TRequestParameters>, ICovariantSearchRequest
2424
where TRequestParameters : IRequestParameters, new()
2525
{
2626
CovariantSearch.CloseOverAutomagicCovariantResultSelector(this.Infer, request);
@@ -30,13 +30,16 @@ private Func<IApiCallDetails, Stream, SearchResponse<TResult>> CreateSearchDeser
3030

3131
private SearchResponse<TResult> FieldsSearchDeserializer<T, TResult, TRequest, TRequestParameters>(IApiCallDetails response, Stream stream, TRequest d)
3232
where T : class
33-
where TResult : class
34-
where TRequest : IRequest<TRequestParameters>, ICovariantSearchRequest
35-
where TRequestParameters : IRequestParameters, new() =>
36-
!response.Success
33+
where TResult : class
34+
where TRequest : IRequest<TRequestParameters>, ICovariantSearchRequest
35+
where TRequestParameters : IRequestParameters, new()
36+
{
37+
var converter = new ConcreteTypeConverter<TResult>(d.TypeSelector);
38+
var serializer = this.Serializer as JsonNetSerializer ?? new JsonNetSerializer(this.ConnectionSettings);
39+
serializer.Initialize(converter);
40+
return !response.Success
3741
? null
38-
: new JsonNetSerializer(this.ConnectionSettings, new ConcreteTypeConverter<TResult>(d.TypeSelector))
39-
.Deserialize<SearchResponse<TResult>>(stream);
40-
42+
: serializer.Deserialize<SearchResponse<TResult>>(stream);
43+
}
4144
}
42-
}
45+
}

src/Nest/Search/SearchTemplate/ElasticClient-SearchTemplate.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,10 @@ private SearchResponse<TResult> FieldsSearchDeserializer<T, TResult>(IApiCallDet
100100
where TResult : class
101101
{
102102
var converter = this.CreateCovariantSearchSelector<T, TResult>(d);
103+
var serializer = this.Serializer as JsonNetSerializer ?? new JsonNetSerializer(this.ConnectionSettings);
104+
serializer.Initialize(converter);
103105
var dict = response.Success
104-
? new JsonNetSerializer(this.ConnectionSettings, converter).Deserialize<SearchResponse<TResult>>(stream)
106+
? serializer.Deserialize<SearchResponse<TResult>>(stream)
105107
: null;
106108
return dict;
107109
}

src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,22 @@ public void ConfiguringSSL()
275275
*/
276276
public class MyJsonNetSerializer : JsonNetSerializer
277277
{
278-
public MyJsonNetSerializer(IConnectionSettingsValues settings) : base(settings) { }
278+
private string _assignedInConstructor;
279+
public MyJsonNetSerializer(IConnectionSettingsValues settings) : base(settings)
280+
{
281+
this._assignedInConstructor = "foo";
282+
}
279283

280284
public int CallToModify { get; set; } = 0;
281285

282-
protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings) => ++CallToModify; //<1> Override ModifyJsonSerializerSettings if you need access to `JsonSerializerSettings`
286+
public string MyProperty { get; set; }
287+
288+
//<1> Override ModifyJsonSerializerSettings if you need access to `JsonSerializerSettings`
289+
protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings)
290+
{
291+
++CallToModify;
292+
MyProperty = _assignedInConstructor;
293+
}
283294

284295
public int CallToContractConverter { get; set; } = 0;
285296

@@ -307,7 +318,7 @@ public void ModifyJsonSerializerSettingsIsCalled()
307318
client.RootNodeInfo();
308319
var serializer = ((IConnectionSettingsValues)settings).Serializer as MyJsonNetSerializer;
309320
serializer.CallToModify.Should().BeGreaterThan(0);
310-
321+
serializer.MyProperty.Should().Be("foo");
311322
serializer.SerializeToString(new Project { });
312323
serializer.CallToContractConverter.Should().BeGreaterThan(0);
313324
}

src/Tests/Document/Multiple/MultiGet/GetManyApiTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using Tests.Framework.Integration;
1010
using Tests.Framework.MockData;
1111
using Xunit;
12+
using Elasticsearch.Net;
13+
using Newtonsoft.Json;
1214

1315
namespace Tests.Document.Multiple.MultiGet
1416
{
@@ -52,5 +54,37 @@ public async Task UsesDefaultIndexAndInferredTypeAsync()
5254
hit.Found.Should().BeTrue();
5355
}
5456
}
57+
58+
[U]
59+
public void UsesCustomSerializationSettings()
60+
{
61+
const string expectedDateString = "2015-02-06T23:45:05Z";
62+
var jsonResponse = $@"{{ ""docs"": [ {{ ""_id"": ""1"", ""_source"": {{ ""dateString"": ""{expectedDateString}"" }}}}]}}";
63+
64+
var connection = new InMemoryConnection(Encoding.UTF8.GetBytes(jsonResponse));
65+
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
66+
var connectionSettings = new ConnectionSettings(connectionPool, connection, settings => new LocalJsonNetSerializer(settings)).DefaultIndex("default-index");
67+
var client = new ElasticClient(connectionSettings);
68+
69+
var hit = client.GetMany<HasDateString>(new[] { "1" })?.FirstOrDefault();
70+
hit.Should().NotBeNull();
71+
hit.Source.Should().NotBeNull();
72+
hit.Source.DateString.Should().Be(expectedDateString);
73+
}
74+
75+
private sealed class HasDateString
76+
{
77+
public string DateString { get; set; }
78+
}
79+
80+
private sealed class LocalJsonNetSerializer : JsonNetSerializer
81+
{
82+
public LocalJsonNetSerializer(IConnectionSettingsValues settings) : base(settings) { }
83+
84+
protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings)
85+
{
86+
settings.DateParseHandling = DateParseHandling.None;
87+
}
88+
}
5589
}
5690
}

src/Tests/Framework/TestClient.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ public static IElasticClient GetFixedReturnClient(
5454
string contentType = "application/json",
5555
Exception exception = null)
5656
{
57-
var serializer = new JsonNetSerializer(new ConnectionSettings());
5857
byte[] fixedResult;
5958

6059
if (contentType == "application/json")
6160
{
61+
var serializer = new JsonNetSerializer(new ConnectionSettings());
62+
serializer.Initialize();
6263
using (var ms = new MemoryStream())
6364
{
6465
serializer.Serialize(response, ms);

0 commit comments

Comments
 (0)