-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Honor PropertyNamingPolicy, PropertyNameCaseInsensitive, & Encoder options when (de)serializing KeyValuePair instances #36424
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
Changes from all commits
f876095
3b44403
aea91eb
ee45b87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,23 +3,52 @@ | |
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Text.Encodings.Web; | ||
|
|
||
| namespace System.Text.Json.Serialization.Converters | ||
| { | ||
| internal sealed class KeyValuePairConverter<TKey, TValue> : JsonValueConverter<KeyValuePair<TKey, TValue>> | ||
| { | ||
| private const string KeyName = "Key"; | ||
| private const string ValueName = "Value"; | ||
| private const string KeyNameCLR = "Key"; | ||
| private const string ValueNameCLR = "Value"; | ||
|
|
||
| // todo: https://github.com/dotnet/runtime/issues/1197 | ||
| // move these to JsonSerializerOptions and use the proper encoding. | ||
| private static readonly JsonEncodedText _keyName = JsonEncodedText.Encode(KeyName, encoder: null); | ||
| private static readonly JsonEncodedText _valueName = JsonEncodedText.Encode(ValueName, encoder: null); | ||
| // Property name for "Key" and "Value" with Options.PropertyNamingPolicy applied. | ||
| private string _keyName = null!; | ||
| private string _valueName = null!; | ||
|
|
||
| // _keyName and _valueName as JsonEncodedText. | ||
| private JsonEncodedText _keyNameEncoded; | ||
layomia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| private JsonEncodedText _valueNameEncoded; | ||
|
|
||
| // todo: https://github.com/dotnet/runtime/issues/32352 | ||
layomia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // it is possible to cache the underlying converters since this is an internal converter and | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment pertains to looking up the System.String converter and caching that. This is in case someone changes the converter. IMO we should either fix it in this PR\pass or close the issue for 5.0 if we don't want to forward to the "proper" converter.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll prioritize making a PR to close this. |
||
| // an instance is created only once for each JsonSerializerOptions instance. | ||
|
|
||
| internal override void Initialize(JsonSerializerOptions options) | ||
| { | ||
| JsonNamingPolicy? namingPolicy = options.PropertyNamingPolicy; | ||
|
|
||
| if (namingPolicy == null) | ||
| { | ||
| _keyName = KeyNameCLR; | ||
| _valueName = ValueNameCLR; | ||
| } | ||
| else | ||
| { | ||
| _keyName = namingPolicy.ConvertName(KeyNameCLR); | ||
| _valueName = namingPolicy.ConvertName(ValueNameCLR); | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (_keyName == null || _valueName == null) | ||
| { | ||
| ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(namingPolicy); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you consider using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did. It's achievable with some refactoring. We don't create a |
||
| } | ||
| } | ||
|
|
||
| JavaScriptEncoder? encoder = options.Encoder; | ||
| _keyNameEncoded = JsonEncodedText.Encode(_keyName, encoder); | ||
| _valueNameEncoded = JsonEncodedText.Encode(_valueName, encoder); | ||
| } | ||
|
|
||
| internal override bool OnTryRead( | ||
| ref Utf8JsonReader reader, | ||
| Type typeToConvert, JsonSerializerOptions options, | ||
|
|
@@ -44,17 +73,19 @@ internal override bool OnTryRead( | |
| ThrowHelper.ThrowJsonException(); | ||
| } | ||
|
|
||
| bool caseInsensitiveMatch = options.PropertyNameCaseInsensitive; | ||
|
|
||
| string propertyName = reader.GetString()!; | ||
| if (propertyName == KeyName) | ||
| if (FoundKeyProperty(propertyName, caseInsensitiveMatch)) | ||
| { | ||
| reader.ReadWithVerify(); | ||
| k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, KeyName); | ||
| k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, _keyName); | ||
| keySet = true; | ||
| } | ||
| else if (propertyName == ValueName) | ||
| else if (FoundValueProperty(propertyName, caseInsensitiveMatch)) | ||
| { | ||
| reader.ReadWithVerify(); | ||
| v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, ValueName); | ||
| v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, _valueName); | ||
| valueSet = true; | ||
| } | ||
| else | ||
|
|
@@ -70,28 +101,21 @@ internal override bool OnTryRead( | |
| } | ||
|
|
||
| propertyName = reader.GetString()!; | ||
| if (propertyName == KeyName) | ||
| if (!keySet && FoundKeyProperty(propertyName, caseInsensitiveMatch)) | ||
layomia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| reader.ReadWithVerify(); | ||
| k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, KeyName); | ||
| keySet = true; | ||
| k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, _keyName); | ||
| } | ||
| else if (propertyName == ValueName) | ||
| else if (!valueSet && FoundValueProperty(propertyName, caseInsensitiveMatch)) | ||
| { | ||
| reader.ReadWithVerify(); | ||
| v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, ValueName); | ||
| valueSet = true; | ||
| v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, _valueName); | ||
| } | ||
| else | ||
| { | ||
| ThrowHelper.ThrowJsonException(); | ||
| } | ||
|
|
||
| if (!keySet || !valueSet) | ||
| { | ||
| ThrowHelper.ThrowJsonException(); | ||
| } | ||
|
|
||
| reader.ReadWithVerify(); | ||
|
|
||
| if (reader.TokenType != JsonTokenType.EndObject) | ||
|
|
@@ -107,14 +131,28 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, KeyValuePair<TKey, TVal | |
| { | ||
| writer.WriteStartObject(); | ||
|
|
||
| writer.WritePropertyName(_keyName); | ||
| JsonSerializer.Serialize(writer, value.Key, options, ref state, KeyName); | ||
| writer.WritePropertyName(_keyNameEncoded); | ||
| JsonSerializer.Serialize(writer, value.Key, options, ref state, _keyName); | ||
|
|
||
| writer.WritePropertyName(_valueName); | ||
| JsonSerializer.Serialize(writer, value.Value, options, ref state, ValueName); | ||
| writer.WritePropertyName(_valueNameEncoded); | ||
| JsonSerializer.Serialize(writer, value.Value, options, ref state, _valueName); | ||
|
|
||
| writer.WriteEndObject(); | ||
| return true; | ||
| } | ||
|
|
||
| private bool FoundKeyProperty(string propertyName, bool caseInsensitiveMatch) | ||
| { | ||
| return propertyName == _keyName || | ||
| (caseInsensitiveMatch && string.Equals(propertyName, _keyName, StringComparison.OrdinalIgnoreCase)) || | ||
| propertyName == KeyNameCLR; | ||
layomia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| private bool FoundValueProperty(string propertyName, bool caseInsensitiveMatch) | ||
| { | ||
| return propertyName == _valueName || | ||
| (caseInsensitiveMatch && string.Equals(propertyName, _valueName, StringComparison.OrdinalIgnoreCase)) || | ||
| propertyName == ValueNameCLR; | ||
| } | ||
| } | ||
| } | ||
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 we include the naming policy in the message for the
PropertyNamingPolicyused on CLR propertiesas well? Right now it saysThe JSON property name for '{0}.{1}' cannot be 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.
Yes we could throw a more specific message (in a clean up PR), although right now it shouldn't be hard for a caller to tell what caused an exception with this message.