-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Add support for multiple non-string TKey on dictionaries #38056
Conversation
This comment has been minimized.
This comment has been minimized.
Looks like we haven't added parsing/writing logic for every type listed here: https://github.com/dotnet/runtime/pull/32676/files#diff-899ea4f41d4cbaeaa272936e99afcf9aR41-R58. |
I wanted to hold-off extending |
...tem.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
Outdated
Show resolved
Hide resolved
....Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
Outdated
Show resolved
Hide resolved
....Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
Outdated
Show resolved
Hide resolved
|
||
state.Current.JsonPropertyNameAsString = reader.GetString(); | ||
ReadOnlySpan<byte> propertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); | ||
state.Current.DictionaryKeyName = propertyName.ToArray(); |
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.
So this is only used for continuation cases...
...raries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs
Outdated
Show resolved
Hide resolved
...ries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
Outdated
Show resolved
Hide resolved
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
...aries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs
Outdated
Show resolved
Hide resolved
...ries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs
Outdated
Show resolved
Hide resolved
...m.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
Outdated
Show resolved
Hide resolved
....Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
Outdated
Show resolved
Hide resolved
...raries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs
Outdated
Show resolved
Hide resolved
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
Outdated
Show resolved
Hide resolved
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
Outdated
Show resolved
Hide resolved
...braries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs
Show resolved
Hide resolved
...tem.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
Show resolved
Hide resolved
If this is still the case, let's duplicate the code for the worst regressions i.e. >= 4%. |
That's no longer the case, I ran the benchmarks again and the results with 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.
LGTM, thank you!
{ | ||
protected override void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state) | ||
protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) |
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.
Any measurable perf benefit for non-string keys if we make this in TKey key
?
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 don't think it is a good idea to use in
given that none of the supported types would be a readonly struct
or a large struct
. Theoretically there should not be benefit from doing so, on the contrary, the perf. would probably decrease given the fact that a defensive copy needs to be created every time Add(in TKey key, )
is called.
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
Outdated
Show resolved
Hide resolved
JsonConverter GetEnumConverter() | ||
=> (JsonConverter)Activator.CreateInstance( | ||
typeof(EnumConverter<>).MakeGenericType(keyType), | ||
BindingFlags.Instance | BindingFlags.Public, | ||
binder: null, | ||
new object[] { EnumConverterOptions.AllowStrings | EnumConverterOptions.AllowNumbers, this }, | ||
culture: null)!; |
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.
Should this code live in the EnumConverterFactory
?
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 is better to have it here since it is only useful in this method.
I can add a comment explaining that this is performed to support multiple enum
types as dictionary key and we want to use our own defined semantics (we don't want to call a custom converter).
...tem.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs
Outdated
Show resolved
Hide resolved
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
Outdated
Show resolved
Hide resolved
@@ -433,5 +431,14 @@ internal void VerifyWrite(int originalDepth, Utf8JsonWriter writer) | |||
/// <param name="value">The value to convert.</param> | |||
/// <param name="options">The <see cref="JsonSerializerOptions"/> being used.</param> | |||
public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options); | |||
|
|||
internal virtual T ReadWithQuotes(ref Utf8JsonReader reader) |
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 believe we previously discussed this, but if someone creates a custom converter such as for Int32 then they will get the default semantics when using quoted keys, not the custom semantics, correct?
If\when we want to support custom converters for quoted keys, then we can add support for exposing ReadWithQuotes
publicly or find some other way to do this.
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
Outdated
Show resolved
Hide resolved
where TCollection : IDictionary | ||
{ | ||
protected override void Add(in object? value, JsonSerializerOptions options, ref ReadStack state) | ||
protected override void Add(string key, in object? value, JsonSerializerOptions options, ref ReadStack state) |
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 assume IDictionary (non-generic) derived classes can have non-string keys? Do we call ToString() on the underlying object? Do we have tests for this?
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.
IDictionary
does not support desereialization of non-string keys given that we don't have a concrete type to parse to, we only use string
keys in this case.
: DictionaryDefaultConverter<TCollection, TValue> | ||
where TCollection : IDictionary<string, TValue> | ||
internal sealed class IDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue> | ||
: DictionaryDefaultConverter<TCollection, TKey, TValue> |
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.
Nice job that we can use use a single class for this.
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
Show resolved
Hide resolved
private static ConcurrentDictionary<Type, JsonConverter> GetDictionaryKeyConverters() | ||
{ | ||
const int NumberOfConverters = 18; | ||
var converters = new ConcurrentDictionary<Type, JsonConverter>(Environment.ProcessorCount, NumberOfConverters); |
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.
nit: if we keep this cache, use the parameterless ConcurrentDictionary ctor here? See #36726 (comment) for a related discussion.
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.
The parameterless ctor internally uses the following.
private static int DefaultConcurrencyLevel => Environment.ProcessorCount;
This would achieve the same with the bonus of allowing us define a initial capacity to avoid resizing over and over.
The difference with #36726 (comment) is that here, the capacity will only grow in the Enum
case, which I don't think is very common.
Second attempt to fix #30524 (previos attempt: #32909)
Enables support for multiple non-string Dictionary key types.
For
object
keys the following applies:The
DictionaryKeyPolicy
defined onJsonSerializerOptions
will only apply for keys of typestring
.This PR also defers unsupported key validation i.e. we will throw NSE until we use the Key Converter, meaning that empty or null dictionaries won't throw even when their key is not supported.