-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Lazily resolve JsonSerializers in JsonNetSerializer #2183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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<T> 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.
@karmi fixed author and email |
Thanks for this @tsliang 👍 For the most part, this LGTM. However, calling a virtual method in a base constructor as we're doing here when calling |
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
@gmarz I usually try to avoid construct-with-init (hence the use of Lazy) - IMHO classes should be fully initialized upon construction and not require a separate init method to be called afterwards, lest someone forget to call the init method after construction. Still, this should get the job done. What is the general ES policy on construct-with-init? Are those used a lot in the project? |
What about custom resolvers? Right now I have problems setting my own :( |
@SayliS what sort of problems? |
@tsliang I totally agree but I think this is a scenario where I can live with it, especially because it gives us a way to solve #2183 as well. I like the idea of using the lazy constructs, but still feels like we're working around the problem rather than fixing the poor design.
We have no policy on this and not something we do a lot at all. This may be one of the only instances. |
@gmarz ok, that makes sense. I have proposed one other solution on #2189. Maybe you can take a look and see what you think? I think it'll solve the problem without requiring any significant changes in how the JsonNetSerializer is used. If it doesn't suit you, though, then let's just do #2189 and I'll close this PR. |
@SayliS I'm assuming you mean adding your own custom converters? You can do this by overriding ContractConverters in your custom serializer. Is that not working for you? |
@SayliS is talking about Newtonsoft-Json Resolvers actually. The problem is that it is impossible to use dependency injection with virtual methods called in the base class. Here is an example what is my problem and @SayliS https://msdn.microsoft.com/en-us/library/ms182331.aspx?f=255&MSPPError=-2147217396 |
Taking another approach then #2189. Here we yield responsibility of creating a new client to ISerializerFactory. The overloads taking a Func factory are now obsolete and others taking ISerializerFactory are added applied feedback on #2197 fix null reference is test setup post switch to serializerfactory
Taking another approach then #2189. Here we yield responsibility of creating a new client to ISerializerFactory. The overloads taking a Func factory are now obsolete and others taking ISerializerFactory are added applied feedback on #2197 fix null reference is test setup post switch to serializerfactory
Taking another approach then #2189. Here we yield responsibility of creating a new client to ISerializerFactory. The overloads taking a Func factory are now obsolete and others taking ISerializerFactory are added applied feedback on #2197 fix null reference is test setup post switch to serializerfactory
…) was being injected, because we use a custom ConnectionSettingsAwareSerializerBase implementation in our tests this was not caught. Also this PR renames our internal json converter from JsonNetSerializer to InternalSerializer, otherwise you would get a confusing error message about using an internal type before actually referencing Nest.JsonNetSerializer
…) was being injected, because we use a custom ConnectionSettingsAwareSerializerBase implementation in our tests this was not caught. Also this PR renames our internal json converter from JsonNetSerializer to InternalSerializer, otherwise you would get a confusing error message about using an internal type before actually referencing Nest.JsonNetSerializer
…) was being injected, because we use a custom ConnectionSettingsAwareSerializerBase implementation in our tests this was not caught. Also this PR renames our internal json converter from JsonNetSerializer to InternalSerializer, otherwise you would get a confusing error message about using an internal type before actually referencing Nest.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:
Call subclass constructor with parameter A, which will set subclass
field A; field A will be used in ModifyJsonSerializerSettings.
Subclass constructor calls JsonNetSerializer constructor with
:base()
JsonNetSerializer constructor calls ModifyJsonSerializerSettings
as part of JsonSerializer.Create
ModifyJsonSerializerSettings makes use of default value for
field A, because it has not been set yet.
JsonSerializer constructor exits
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.