-
Notifications
You must be signed in to change notification settings - Fork 0
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
Make contexts more combinator friendly #9
Make contexts more combinator friendly #9
Conversation
krwq
commented
Jun 9, 2022
- we don't need new API in JsonSerializeContext
- we do not need to create new instance of context inside of context.GetTypeInfo
- converters work more consistently
- you can customize child type info of contexts now if you combine (previously it was hardcoded)
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
...braries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
Outdated
Show resolved
Hide resolved
adedb6b
to
93cbac4
Compare
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
...braries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
Outdated
Show resolved
Hide resolved
...braries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
Outdated
Show resolved
Hide resolved
...braries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
Show resolved
Hide resolved
...braries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
Show resolved
Hide resolved
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
Outdated
Show resolved
Hide resolved
{ | ||
// Only cache the value once (de)serialization has occurred since new converters can be added that may change the result. | ||
if (_cachingContext != null) | ||
JsonConverter? converter = GetOrAddJsonTypeInfoEx(typeToConvert, configured: false, doNotThrow: true)?.Converter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this method is "NotCached", why is it calling into GetOrAddJsonTypeInfoEx
which does consult the cache? It basically means that a cache miss will result in multiple lookups for no clear reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NotCached refers to converter cache not JsonTypeInfo cache
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a comment on top to make that clear
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// <summary>
/// Gets converter from type info without using converter cache.
/// This method is used by converter cache. It may use type info cache.
/// </summary>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a question: do we need to keep the converter cache if we're moving the source truth for converters to metadata?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For regular scenario it's technically not needed but:
- GetConverter also needs to return answer from options and reflection even if type info is null (for compat)
- we previously cached instance which technically means we always got exactly same instance after first usage, if I removed the converter caching it will still be cached if resolver returned non-null though so that might be ok
I'm ok with removing that but we need to agree above is ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's ok to return a non-cached converter for GetConverter call not backed by JsonTypeInfo. Are there any cases in the hot path where it might result in unnecessary recalculations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, we're gonna cache everything in the hot paths; will remove converter cache
|
||
if (converter == null) | ||
{ | ||
ThrowHelper.ThrowNotSupportedException_BuiltInConvertersNotRooted(typeToConvert); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems wrong. Why would we throw this error if JsonSerializerOptions.Converters
doesn't contain a compatible converter? As the name suggests the error is meant to be thrown if the built-in converters have not been rooted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned in other places, we don't throw here unless:
- rooting logic was never exercised
- we somehow ended up using resolver which source gen did not generate type info code for (that's what we also did before this change) - same applies if multiple contexts are combined
return converter; | ||
} | ||
|
||
private JsonConverter? GetConverterFromOptions(Type typeToConvert) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current behaviour we want to return the built-in converters (or throw an exception if they haven't been rooted yet). This seems like a breaking change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not breaking change. If you call GetConverter directly it will root converter first and then call this method will behave as it was.
On the other hand if this is called when context is bound but converters are not rooted (it may be called implicitly) you will get an exception same way as you used to.
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
Outdated
Show resolved
Hide resolved
} | ||
|
||
internal JsonConverter GetConverterFromType(Type typeToConvert) | ||
private JsonConverter GetConverterFromOptionsOrReflectionConverter(Type typeToConvert) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling it ResolverConverter
over GetConverter
might be better at emphasizing this is not caching
private JsonConverter GetConverterFromOptionsOrReflectionConverter(Type typeToConvert) | |
private JsonConverter ResolveConverter(Type typeToConvert) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this actually isn't resolving converter - this is technically speaking reflection converter logic. From user perspective the order is following:
- type info converter (i.e. serializer context)
- options
- reflection converter (if rooted, if GetConverter called explicitly then this is always rooted)
this method does the last two and it's used by: reflection json type info and as a fallback for GetConverter (the fallback is arguably wrong but since we already shipped like that this logic is preserved here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this actually isn't resolving converter
Arguably it's resolving a converter based on the Converters
list and the built-in stuff. My only concern is that we have accumulated too many JsonSerializer.GetConverter*
method names and it might be in our interest to adopt a separate naming convention for the converter resolution logic that doesn't involve the cache in any way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are only two case which must not use cache and first one could also cause stack overflow: reflection/custom type info resolver which needs to get converter; calling GetConverter before options are locked (caching cannot happen because converters/type infos can still be changed). Everything else is should go through caching
} | ||
|
||
JsonConverter<T> underlyingConverter = GetTypedConverter<T>(options.GetConverterFromTypeInfo(typeof(T))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of lookup seems completely out of place compared to the other JsonMetadataServices
converter components. I'm concerned it might be accidentally rooting reflection components in sourcegen. cc @layomia
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it won't. The only place we do rooting is public GetConverter. This will lead to exception unless you called one of the rooting APIs. This is the reason I refactored GetConverter*
methods so that we preserve this behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another thing to note here is that there are actually only 4 places we get inner converters:
- nullable
- collections
- enumerables
- objects
we do exactly same for all 3 others except it's more indirect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do exactly same for all 3 others except it's more indirect
That's my point actually. The source generator is very intentionally making it so that metadata are composed explicitly rather than performing lookups via indirection to the metadata resolver. Why does nullable need to be different and does it imply that nested metadata resolution semantics diverge compared to, say, collections?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this indirection happens exactly once per type info, later it's cached forever. See this test case: https://github.com/krwq/runtime-1/pull/9/files#diff-9ca8a434d4ee452728961d1242d8680e5f4b72c8973a99b7ef59ecb005541025R131
this is not arguable what the expected behavior should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not disputing that, it's more a question of why this specific type needing to be different. It seems weird that it does and introduces more accidental complexity. Isn't it possible to emulate prior art?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is called from generated code and technically speaking we could call options.GetConverter
but at the same time that currently roots reflection converters - if we do that that would break linker safety
...braries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
Show resolved
Hide resolved
@@ -1,6 +1,7 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks> | |||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> | |||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
<!-- SYSLIB0020: JsonSerializerOptions.IgnoreNullValues is obsolete --> | |||
<NoWarn>$(NoWarn);SYSLIB0020</NoWarn> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests validating the new behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add them as part of Combine tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added one larger test here https://github.com/krwq/runtime-1/pull/9/files#diff-9ca8a434d4ee452728961d1242d8680e5f4b72c8973a99b7ef59ecb005541025R131 exercising all trickier cases. Everything else should be covered by existing test cases
e7b982b
to
bb56c48
Compare
9d39911
to
e3fe6ba
Compare
e3fe6ba
to
1a913d4
Compare
I've pushed a rebased version, will address the comment about removing converter cache as separate commit |
fix build errors Move converter rooting to DefaultJsonTypeInfoResolver so that it can be used standalone Fix ConfigurationList.IsReadOnly Minor refactorings (#1) * Makes the following changes: * Move singleton initialization for DefaultTypeInfoResolver behind a static property. * Consolidate JsonSerializerContext & IJsonTypeInfoResolver values to a single field. * Move reflection fallback logic away from JsonSerializerContext and into JsonSerializerOptions * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs * remove testing of removed field Simplify the JsonTypeInfo.CreateObject implemenetation (#2) * Simplify the JsonTypeInfo.CreateObject implemenetation * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs Co-authored-by: Krzysztof Wicher <[email protected]> Co-authored-by: Krzysztof Wicher <[email protected]> Tests and fixes for JsonTypeInfoKind.None TypeInfo type mismatch tests Allow setting NumberHandling on JsonTypeInfoKind.None test resolver returning wrong type of options JsonTypeInfo/JsonPropertyInfo mutability tests rename test file Move default converter rooting responsibility behind DefaultJsonTypeInfoResolver (#3) * Move default converter rooting responsibility behind DefaultJsonTypeInfoResolver * address feedback Add simple test for using JsonTypeInfo<T> with APIs directly taking it fix and tests for untyped/typed CreateObject uncomment test cases, remove todo More tests and tiny fixes Add a JsonTypeInfoResolver.Combine test for JsonSerializerContext (#4) * Fix JsonTypeInfoResolver.Combine for JsonSerializerContext * Break up failing test Fix simple scenarios for combining contexts (#6) * Fix simple scenarios for combining contexts * feedback JsonSerializerContext combine test with different camel casing Remove unneeded virtual calls & branching when accessing Get & Set delegates (#7) JsonPropertyInfo tests everything minus ShouldSerialize & NumberHandling Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs throw InvalidOperationException rather than ArgumentNullException for source gen when PropertyInfo.Name is assigned through JsonPropertyInfoValues tests for duplicated property names and JsonPropertyInfo.NumberHandling Add tests for NumberHandling and failing tests for ShouldSerialize disable the failing test and add extra checks disable remainder of the failing ShouldSerialize tests, fix working one Fix ShouldSerialize and IgnoreCondition interop Add failing tests for CreateObject + parametrized constructors Fix CreateObject support for JsonConstructor types (#10) * Fix CreateObject support for JsonConstructor types * address feedback Make contexts more combinator friendly (#9) * Make contexts more combinator friendly * remove converter cache
* Initial implementation for contract customization fix build errors Move converter rooting to DefaultJsonTypeInfoResolver so that it can be used standalone Fix ConfigurationList.IsReadOnly Minor refactorings (#1) * Makes the following changes: * Move singleton initialization for DefaultTypeInfoResolver behind a static property. * Consolidate JsonSerializerContext & IJsonTypeInfoResolver values to a single field. * Move reflection fallback logic away from JsonSerializerContext and into JsonSerializerOptions * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs * remove testing of removed field Simplify the JsonTypeInfo.CreateObject implemenetation (#2) * Simplify the JsonTypeInfo.CreateObject implemenetation * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs Co-authored-by: Krzysztof Wicher <[email protected]> Co-authored-by: Krzysztof Wicher <[email protected]> Tests and fixes for JsonTypeInfoKind.None TypeInfo type mismatch tests Allow setting NumberHandling on JsonTypeInfoKind.None test resolver returning wrong type of options JsonTypeInfo/JsonPropertyInfo mutability tests rename test file Move default converter rooting responsibility behind DefaultJsonTypeInfoResolver (#3) * Move default converter rooting responsibility behind DefaultJsonTypeInfoResolver * address feedback Add simple test for using JsonTypeInfo<T> with APIs directly taking it fix and tests for untyped/typed CreateObject uncomment test cases, remove todo More tests and tiny fixes Add a JsonTypeInfoResolver.Combine test for JsonSerializerContext (#4) * Fix JsonTypeInfoResolver.Combine for JsonSerializerContext * Break up failing test Fix simple scenarios for combining contexts (#6) * Fix simple scenarios for combining contexts * feedback JsonSerializerContext combine test with different camel casing Remove unneeded virtual calls & branching when accessing Get & Set delegates (#7) JsonPropertyInfo tests everything minus ShouldSerialize & NumberHandling Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs throw InvalidOperationException rather than ArgumentNullException for source gen when PropertyInfo.Name is assigned through JsonPropertyInfoValues tests for duplicated property names and JsonPropertyInfo.NumberHandling Add tests for NumberHandling and failing tests for ShouldSerialize disable the failing test and add extra checks disable remainder of the failing ShouldSerialize tests, fix working one Fix ShouldSerialize and IgnoreCondition interop Add failing tests for CreateObject + parametrized constructors Fix CreateObject support for JsonConstructor types (#10) * Fix CreateObject support for JsonConstructor types * address feedback Make contexts more combinator friendly (#9) * Make contexts more combinator friendly * remove converter cache * redesign test to account for JsonConstructorAttribute * Combine unit tests * address feedback * Add acceptance tests for DataContract attributes & Specified pattern (#11) * Add private field serialization acceptance test (#13) * tests, PR feedback (#14) * PR feedback and extra tests * Shorten class name, remove incorrect check (not true for polimorphic cases) * Make parameter matching for custom properties map property Name with parameter (#16) * Test static initialization with JsonTypeInfo (#17) * Fix test failures and proper fix this time (#18) * Fix test failures and proper fix this time * reinstate ActiveIssueAttribute * PR feedback and adjust couple of tests which don't set TypeInfoResolver * fix IAsyncEnumerable tests * Lock JsonSerializerOptions in JsonTypeInfo.EnsureConfigured() Co-authored-by: Eirik Tsarpalis <[email protected]> Co-authored-by: Eirik Tsarpalis <[email protected]>