From b42b6f0e07b0d384113b353a9cbe61778d1c0900 Mon Sep 17 00:00:00 2001 From: Tai Sassen-Liang Date: Mon, 1 Aug 2016 11:27:50 +0200 Subject: [PATCH] Lazily resolve JsonSerializers in JsonNetSerializer The current JsonNetSerializer implementation creates the default serializers within its constructor, which then calls ModifyJsonSerializerSettings. This means that a subclass of JsonNetSerializer cannot modify the JsonSerializerSettings with fields set by the constructor, because the order of operations is: 1. Call subclass constructor with parameter A, which will set subclass field A; field A will be used in ModifyJsonSerializerSettings. 2. Subclass constructor calls JsonNetSerializer constructor with :base() 3. JsonNetSerializer constructor calls ModifyJsonSerializerSettings as part of JsonSerializer.Create 4. ModifyJsonSerializerSettings makes use of default value for field A, because it has not been set yet. 5. JsonSerializer constructor exits 6. Subclass constructor sets value of field A from parameter A. By using the Lazy class, we can ensure that ModifyJsonSerializerSettings is called after the subclass constructor has exited, which allows fields to be set and thereby used in ModifyJsonSerializerSettings. The unit test in Connection.doc.cs has been expanded to demonstrate this capability with a (fairly trivial) use case. --- .../JsonNetSerializer.cs | 31 ++++++++++--------- .../ClientConcepts/LowLevel/Connecting.doc.cs | 20 ++++++++++-- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/Nest/CommonAbstractions/SerializationBehavior/JsonNetSerializer.cs b/src/Nest/CommonAbstractions/SerializationBehavior/JsonNetSerializer.cs index c8599387cc2..2fce8fc3bc0 100644 --- a/src/Nest/CommonAbstractions/SerializationBehavior/JsonNetSerializer.cs +++ b/src/Nest/CommonAbstractions/SerializationBehavior/JsonNetSerializer.cs @@ -19,10 +19,10 @@ public class JsonNetSerializer : IElasticsearchSerializer protected ElasticContractResolver ContractResolver { get; } //todo this internal smells - internal JsonSerializer Serializer => _defaultSerializer; + internal JsonSerializer Serializer => _defaultSerializer.Value; - private readonly Dictionary _defaultSerializers; - private readonly JsonSerializer _defaultSerializer; + private readonly Lazy> _defaultSerializers; + private readonly Lazy _defaultSerializer; protected virtual void ModifyJsonSerializerSettings(JsonSerializerSettings settings) { } protected virtual IList> ContractConverters => null; @@ -30,29 +30,30 @@ protected virtual void ModifyJsonSerializerSettings(JsonSerializerSettings setti public JsonNetSerializer(IConnectionSettingsValues settings) : this(settings, null) { } /// - /// this constructor is only here for stateful (de)serialization + /// this constructor is only here for stateful (de) serialization /// internal JsonNetSerializer(IConnectionSettingsValues settings, JsonConverter stateFullConverter) { this.Settings = settings; var piggyBackState = stateFullConverter == null ? null : new JsonConverterPiggyBackState { ActualJsonConverter = stateFullConverter }; + // ReSharper disable once VirtualMemberCallInContructor this.ContractResolver = new ElasticContractResolver(this.Settings, this.ContractConverters) { PiggyBackState = piggyBackState }; - this._defaultSerializer = JsonSerializer.Create(this.CreateSettings(SerializationFormatting.None)); - //this._defaultSerializer.Formatting = Formatting.None; - var indentedSerializer = JsonSerializer.Create(this.CreateSettings(SerializationFormatting.Indented)); - //indentedSerializer.Formatting = Formatting.Indented; - this._defaultSerializers = new Dictionary + this._defaultSerializer = new Lazy(() => JsonSerializer.Create(this.CreateSettings(SerializationFormatting.None))); + + var indentedSerializer = new Lazy(() => JsonSerializer.Create(this.CreateSettings(SerializationFormatting.Indented))); + + this._defaultSerializers = new Lazy>(() => new Dictionary() { - { SerializationFormatting.None, this._defaultSerializer }, - { SerializationFormatting.Indented, indentedSerializer } - }; + { SerializationFormatting.None, this._defaultSerializer.Value }, + { SerializationFormatting.Indented, indentedSerializer.Value } + }); } public virtual void Serialize(object data, Stream writableStream, SerializationFormatting formatting = SerializationFormatting.Indented) { - var serializer = _defaultSerializers[formatting]; + var serializer = _defaultSerializers.Value[formatting]; using (var writer = new StreamWriter(writableStream, ExpectedEncoding, 8096, leaveOpen: true)) using (var jsonWriter = new JsonTextWriter(writer)) { @@ -87,7 +88,7 @@ public virtual T Deserialize(Stream stream) using (var streamReader = new StreamReader(stream)) using (var jsonTextReader = new JsonTextReader(streamReader)) { - var t = this._defaultSerializer.Deserialize(jsonTextReader, typeof(T)); + var t = this.Serializer.Deserialize(jsonTextReader, typeof(T)); return (T)t; } } @@ -117,4 +118,4 @@ private JsonSerializerSettings CreateSettings(SerializationFormatting formatting return settings; } } -} \ No newline at end of file +} diff --git a/src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs b/src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs index 3b92edc63c0..1231d43decf 100644 --- a/src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs +++ b/src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs @@ -274,11 +274,23 @@ public void ConfiguringSSL() */ public class MyJsonNetSerializer : JsonNetSerializer { - public MyJsonNetSerializer(IConnectionSettingsValues settings) : base(settings) { } + private int _maxDepth; + + public MyJsonNetSerializer(IConnectionSettingsValues settings, int maxDepth) + : base(settings) + { + this._maxDepth = maxDepth; + } public int CallToModify { get; set; } = 0; - protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings) => ++CallToModify; //<1> Override ModifyJsonSerializerSettings if you need access to `JsonSerializerSettings` + public int SetMaxDepth { get; set; } + + protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings) + { + ++CallToModify; + SetMaxDepth = _maxDepth; + } //<1> Override ModifyJsonSerializerSettings if you need access to `JsonSerializerSettings` public int CallToContractConverter { get; set; } = 0; @@ -300,7 +312,8 @@ public MyJsonNetSerializer(IConnectionSettingsValues settings) : base(settings) public void ModifyJsonSerializerSettingsIsCalled() { var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); - var settings = new ConnectionSettings(connectionPool, new InMemoryConnection(), s => new MyJsonNetSerializer(s)); + var maxDepth = 8; + var settings = new ConnectionSettings(connectionPool, new InMemoryConnection(), s => new MyJsonNetSerializer(s, maxDepth)); var client = new ElasticClient(settings); client.RootNodeInfo(); client.RootNodeInfo(); @@ -309,6 +322,7 @@ public void ModifyJsonSerializerSettingsIsCalled() serializer.SerializeToString(new Project { }); serializer.CallToContractConverter.Should().BeGreaterThan(0); + serializer.SetMaxDepth.Should().Be(maxDepth); } } }