<data name="DefaultIgnoreConditionInvalid" xml:space="preserve">
<value>The value cannot be 'JsonIgnoreCondition.Always'.</value>
</data>
+ <data name="FormatBoolean" xml:space="preserve">
+ <value>The JSON value is not in a supported Boolean format.</value>
+ </data>
+ <data name="DictionaryKeyTypeNotSupported" xml:space="preserve">
+ <value>The type '{0}' is not a supported Dictionary key type.</value>
+ </data>
</root>
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ConcurrentQueueOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ConcurrentStackOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\DictionaryDefaultConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\Collection\DictionaryOfStringTValueConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\Converters\Collection\DictionaryOfTKeyTValueConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ICollectionOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IDictionaryConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\Collection\IDictionaryOfStringTValueConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\Converters\Collection\IDictionaryOfTKeyTValueConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IEnumerableConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IEnumerableConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IEnumerableConverterFactoryHelpers.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IEnumerableWithAddMethodConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IListConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\IListOfTConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\Collection\ImmutableDictionaryOfStringTValueConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\Converters\Collection\ImmutableDictionaryOfTKeyTValueConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ImmutableEnumerableOfTConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\Collection\IReadOnlyDictionaryOfStringTValueConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\Converters\Collection\IReadOnlyDictionaryOfTKeyTValueConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ISetOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\JsonCollectionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\JsonDictionaryConverter.cs" />
public const int MaxBase64ValueTokenSize = (MaxEscapedTokenSize >> 2) * 3 / MaxExpansionFactorWhileEscaping; // 125_000_000 bytes
public const int MaxCharacterTokenSize = MaxEscapedTokenSize / MaxExpansionFactorWhileEscaping; // 166_666_666 characters
+ public const int MaximumFormatBooleanLength = 5;
public const int MaximumFormatInt64Length = 20; // 19 + sign (i.e. -9223372036854775808)
public const int MaximumFormatUInt64Length = 20; // i.e. 18446744073709551615
public const int MaximumFormatDoubleLength = 128; // default (i.e. 'G'), using 128 (rather than say 32) to be future-proof.
using System.Buffers.Text;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
namespace System.Text.Json
{
return value;
}
+ internal byte GetByteWithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetByteCore(out byte value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Byte);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as an <see cref="sbyte"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to an <see cref="sbyte"/>
return value;
}
+ internal sbyte GetSByteWithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetSByteCore(out sbyte value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.SByte);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="short"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="short"/>
return value;
}
+ internal short GetInt16WithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetInt16Core(out short value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Int16);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as an <see cref="int"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to an <see cref="int"/>
return value;
}
+ internal int GetInt32WithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetInt32Core(out int value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Int32);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="long"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="long"/>
return value;
}
+ internal long GetInt64WithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetInt64Core(out long value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Int64);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="ushort"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="ushort"/>
return value;
}
+ internal ushort GetUInt16WithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetUInt16Core(out ushort value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.UInt16);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="uint"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="uint"/>
return value;
}
+ internal uint GetUInt32WithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetUInt32Core(out uint value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.UInt32);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="ulong"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="ulong"/>
return value;
}
+ internal ulong GetUInt64WithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetUInt64Core(out ulong value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.UInt64);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="float"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="float"/>
return value;
}
+ internal float GetSingleWithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetSingleCore(out float value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Single);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="double"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="double"/>
return value;
}
+ internal double GetDoubleWithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetDoubleCore(out double value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Double);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="decimal"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="decimal"/>
return value;
}
+ internal decimal GetDecimalWithQuotes()
+ {
+ ReadOnlySpan<byte> span = GetUnescapedSpan();
+ if (!TryGetDecimalCore(out decimal value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Decimal);
+ }
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="DateTime"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="DateTime"/>
return value;
}
+ internal DateTime GetDateTimeNoValidation()
+ {
+ if (!TryGetDateTimeCore(out DateTime value))
+ {
+ throw ThrowHelper.GetFormatException(DataType.DateTime);
+ }
+
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="DateTimeOffset"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="DateTimeOffset"/>
return value;
}
+ internal DateTimeOffset GetDateTimeOffsetNoValidation()
+ {
+ if (!TryGetDateTimeOffsetCore(out DateTimeOffset value))
+ {
+ throw ThrowHelper.GetFormatException(DataType.DateTimeOffset);
+ }
+
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="Guid"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a <see cref="Guid"/>
return value;
}
+ internal Guid GetGuidNoValidation()
+ {
+ if (!TryGetGuidCore(out Guid value))
+ {
+ throw ThrowHelper.GetFormatException(DataType.Guid);
+ }
+
+ return value;
+ }
+
/// <summary>
/// Parses the current JSON token value from the source and decodes the Base64 encoded JSON string as bytes.
/// Returns <see langword="true"/> if the entire token value is encoded as valid Base64 text and can be successfully
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetByteCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetByteCore(out byte value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out byte tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetSByteCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetSByteCore(out sbyte value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out sbyte tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetInt16Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetInt16Core(out short value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out short tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetInt32Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetInt32Core(out int value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetInt64Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetInt64Core(out long value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out long tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetUInt16Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetUInt16Core(out ushort value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out ushort tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetUInt32Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetUInt32Core(out uint value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out uint tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetUInt64Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetUInt64Core(out ulong value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out ulong tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetSingleCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetSingleCore(out float value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out float tmp, out int bytesConsumed, _numberFormat)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetDoubleCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetDoubleCore(out double value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out double tmp, out int bytesConsumed, _numberFormat)
&& span.Length == bytesConsumed)
{
}
ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetDecimalCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetDecimalCore(out decimal value, ReadOnlySpan<byte> span)
+ {
if (Utf8Parser.TryParse(span, out decimal tmp, out int bytesConsumed, _numberFormat)
&& span.Length == bytesConsumed)
{
throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
}
+ return TryGetDateTimeCore(out value);
+ }
+
+ internal bool TryGetDateTimeCore(out DateTime value)
+ {
ReadOnlySpan<byte> span = stackalloc byte[0];
if (HasValueSequence)
throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
}
+ return TryGetDateTimeOffsetCore(out value);
+ }
+
+ internal bool TryGetDateTimeOffsetCore(out DateTimeOffset value)
+ {
ReadOnlySpan<byte> span = stackalloc byte[0];
if (HasValueSequence)
throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
}
+ return TryGetGuidCore(out value);
+ }
+
+ internal bool TryGetGuidCore(out Guid value)
+ {
ReadOnlySpan<byte> span = stackalloc byte[0];
if (HasValueSequence)
JsonTokenType.True => nameof(JsonTokenType.True),
_ => ((byte)TokenType).ToString()
};
+
+ private ReadOnlySpan<byte> GetUnescapedSpan()
+ {
+ ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ if (_stringHasEscaping)
+ {
+ int idx = span.IndexOf(JsonConstants.BackSlash);
+ Debug.Assert(idx != -1);
+ span = JsonReaderHelper.GetUnescapedSpan(span, idx);
+ }
+
+ return span;
+ }
}
}
/// <summary>
/// Default base class implementation of <cref>JsonDictionaryConverter{TCollection}</cref> .
/// </summary>
- internal abstract class DictionaryDefaultConverter<TCollection, TValue>
+ internal abstract class DictionaryDefaultConverter<TCollection, TKey, TValue>
: JsonDictionaryConverter<TCollection>
+ where TKey : notnull
{
/// <summary>
/// When overridden, adds the value to the collection.
/// </summary>
- protected abstract void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state);
+ protected abstract void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state);
/// <summary>
/// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.
internal override Type ElementType => typeof(TValue);
- protected static JsonConverter<TValue> GetElementConverter(ref ReadStack state)
- {
- JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.JsonClassInfo.ElementClassInfo!.PropertyInfoForClassInfo.ConverterBase;
- Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
-
- return converter;
- }
-
- protected string GetKeyName(string key, ref WriteStack state, JsonSerializerOptions options)
- {
- if (options.DictionaryKeyPolicy != null && !state.Current.IgnoreDictionaryKeyPolicy)
- {
- key = options.DictionaryKeyPolicy.ConvertName(key);
-
- if (key == null)
- {
- ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(options.DictionaryKeyPolicy);
- }
- }
+ protected Type KeyType = typeof(TKey);
+ // For string keys we don't use a key converter
+ // in order to avoid performance regression on already supported types.
+ protected bool IsStringKey = typeof(TKey) == typeof(string);
- return key;
- }
+ protected JsonConverter<TKey>? _keyConverter;
+ protected JsonConverter<TValue>? _valueConverter;
- protected static JsonConverter<TValue> GetValueConverter(ref WriteStack state)
+ protected static JsonConverter<TValue> GetValueConverter(JsonClassInfo classInfo)
{
- JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.DeclaredJsonPropertyInfo!.ConverterBase;
+ JsonConverter<TValue> converter = (JsonConverter<TValue>)classInfo.ElementClassInfo!.PropertyInfoForClassInfo.ConverterBase;
Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
return converter;
}
+ protected static JsonConverter<TKey> GetKeyConverter(Type keyType, JsonSerializerOptions options)
+ => (JsonConverter<TKey>)options.GetDictionaryKeyConverter(keyType);
+
internal sealed override bool OnTryRead(
ref Utf8JsonReader reader,
Type typeToConvert,
CreateCollection(ref reader, ref state);
- JsonConverter<TValue> elementConverter = GetElementConverter(ref state);
- if (elementConverter.CanUseDirectReadOrWrite)
+ JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
+ if (valueConverter.CanUseDirectReadOrWrite)
{
// Process all elements.
while (true)
// Read method would have thrown if otherwise.
Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
- state.Current.JsonPropertyNameAsString = reader.GetString();
+ TKey key = ReadDictionaryKey(ref reader, ref state);
// Read the value and add.
reader.ReadWithVerify();
- TValue element = elementConverter.Read(ref reader, typeof(TValue), options);
- Add(element!, options, ref state);
+ TValue element = valueConverter.Read(ref reader, typeof(TValue), options);
+ Add(key, element!, options, ref state);
}
}
else
// Read method would have thrown if otherwise.
Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
- state.Current.JsonPropertyNameAsString = reader.GetString();
+ TKey key = ReadDictionaryKey(ref reader, ref state);
reader.ReadWithVerify();
// Get the value from the converter and add it.
- elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element);
- Add(element!, options, ref state);
+ valueConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element);
+ Add(key, element!, options, ref state);
}
}
}
}
// Process all elements.
- JsonConverter<TValue> elementConverter = GetElementConverter(ref state);
+ JsonConverter<TValue> elementConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
while (true)
{
if (state.Current.PropertyState == StackFramePropertyState.None)
state.Current.PropertyState = StackFramePropertyState.Name;
- // Verify property doesn't contain metadata.
if (preserveReferences)
{
ReadOnlySpan<byte> propertyName = reader.GetSpan();
}
}
- state.Current.JsonPropertyNameAsString = reader.GetString();
+ state.Current.DictionaryKey = ReadDictionaryKey(ref reader, ref state);
}
if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
return false;
}
- Add(element!, options, ref state);
+ TKey key = (TKey)state.Current.DictionaryKey!;
+ Add(key, element!, options, ref state);
state.Current.EndElement();
}
}
ConvertCollection(ref state, options);
value = (TCollection)state.Current.ReturnValue!;
return true;
+
+ TKey ReadDictionaryKey(ref Utf8JsonReader reader, ref ReadStack state)
+ {
+ TKey key;
+ string unescapedPropertyNameAsString;
+
+ // Special case string to avoid calling GetString twice and save one allocation.
+ if (IsStringKey)
+ {
+ unescapedPropertyNameAsString = reader.GetString()!;
+ key = (TKey)(object)unescapedPropertyNameAsString;
+ }
+ else
+ {
+ JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ key = keyConverter.ReadWithQuotes(ref reader);
+ unescapedPropertyNameAsString = reader.GetString()!;
+ }
+
+ // Copy key name for JSON Path support in case of error.
+ state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString;
+ return key;
+ }
}
internal sealed override bool OnTryWrite(
/// Converter for Dictionary{string, TValue} that (de)serializes as a JSON object with properties
/// representing the dictionary element key and value.
/// </summary>
- internal sealed class DictionaryOfStringTValueConverter<TCollection, TValue>
- : DictionaryDefaultConverter<TCollection, TValue>
- where TCollection : Dictionary<string, TValue>
+ internal sealed class DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>
+ : DictionaryDefaultConverter<TCollection, TKey, TValue>
+ where TCollection : Dictionary<TKey, TValue>
+ where TKey : notnull
{
- 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)
{
- string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
}
JsonSerializerOptions options,
ref WriteStack state)
{
- Dictionary<string, TValue>.Enumerator enumerator;
+ Dictionary<TKey, TValue>.Enumerator enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
}
else
{
- enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
+ enumerator = (Dictionary<TKey, TValue>.Enumerator)state.Current.CollectionEnumerator;
}
- JsonConverter<TValue> converter = GetValueConverter(ref state);
- if (!state.SupportContinuation && converter.CanUseDirectReadOrWrite)
+ JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
+ if (!state.SupportContinuation && valueConverter.CanUseDirectReadOrWrite)
{
// Fast path that avoids validation and extra indirection.
do
{
- string key = GetKeyName(enumerator.Current.Key, ref state, options);
- writer.WritePropertyName(key);
- converter.Write(writer, enumerator.Current.Value, options);
+ TKey key = enumerator.Current.Key;
+ keyConverter.WriteWithQuotes(writer, key, options, ref state);
+
+ valueConverter.Write(writer, enumerator.Current.Value, options);
} while (enumerator.MoveNext());
}
else
if (state.Current.PropertyState < StackFramePropertyState.Name)
{
state.Current.PropertyState = StackFramePropertyState.Name;
- string key = GetKeyName(enumerator.Current.Key, ref state, options);
- writer.WritePropertyName(key);
+
+ TKey key = enumerator.Current.Key;
+ keyConverter.WriteWithQuotes(writer, key, options, ref state);
}
TValue element = enumerator.Current.Value;
- if (!converter.TryWrite(writer, element, options, ref state))
+ if (!valueConverter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
/// representing the dictionary element key and value.
/// </summary>
internal sealed class IDictionaryConverter<TCollection>
- : DictionaryDefaultConverter<TCollection, object?>
+ : DictionaryDefaultConverter<TCollection, string, object?>
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)
{
- string key = state.Current.JsonPropertyNameAsString!;
((IDictionary)state.Current.ReturnValue!)[key] = value;
}
+ private JsonConverter<object>? _objectConverter;
+
+ private static JsonConverter<object> GetObjectKeyConverter(JsonSerializerOptions options)
+ => (JsonConverter<object>)options.GetDictionaryKeyConverter(typeof(object));
+
protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state)
{
JsonClassInfo classInfo = state.Current.JsonClassInfo;
enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator;
}
- JsonConverter<object?> converter = GetValueConverter(ref state);
+ JsonConverter<object?> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
do
{
if (ShouldFlush(writer, ref state))
if (state.Current.PropertyState < StackFramePropertyState.Name)
{
state.Current.PropertyState = StackFramePropertyState.Name;
-
- if (enumerator.Key is string key)
+ object key = enumerator.Key;
+ // Optimize for string since that's the hot path.
+ if (key is string keyString)
{
- key = GetKeyName(key, ref state, options);
- writer.WritePropertyName(key);
+ JsonConverter<string> stringKeyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ stringKeyConverter.WriteWithQuotes(writer, keyString, options, ref state);
}
else
{
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.DeclaredJsonPropertyInfo!.RuntimePropertyType!);
+ // IDictionary is a special case since it has polymorphic object semantics on serialization
+ // but needs to use JsonConverter<string> on deserialization.
+ JsonConverter<object> objectKeyConverter = _objectConverter ??= GetObjectKeyConverter(options);
+ objectKeyConverter.WriteWithQuotes(writer, key, options, ref state);
}
}
object? element = enumerator.Value;
- if (!converter.TryWrite(writer, element, options, ref state))
+ if (!valueConverter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
/// Converter for <cref>System.Collections.Generic.IDictionary{string, TValue}</cref> that
/// (de)serializes as a JSON object with properties representing the dictionary element key and value.
/// </summary>
- internal sealed class IDictionaryOfStringTValueConverter<TCollection, TValue>
- : DictionaryDefaultConverter<TCollection, TValue>
- where TCollection : IDictionary<string, TValue>
+ internal sealed class IDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>
+ : DictionaryDefaultConverter<TCollection, TKey, TValue>
+ where TCollection : IDictionary<TKey, TValue>
+ where TKey : notnull
{
- 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)
{
- string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
}
JsonSerializerOptions options,
ref WriteStack state)
{
- IEnumerator<KeyValuePair<string, TValue>> enumerator;
+ IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
}
else
{
- enumerator = (IEnumerator<KeyValuePair<string, TValue>>)state.Current.CollectionEnumerator;
+ enumerator = (IEnumerator<KeyValuePair<TKey, TValue>>)state.Current.CollectionEnumerator;
}
- JsonConverter<TValue> converter = GetValueConverter(ref state);
+ JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
do
{
if (ShouldFlush(writer, ref state))
if (state.Current.PropertyState < StackFramePropertyState.Name)
{
state.Current.PropertyState = StackFramePropertyState.Name;
- string key = GetKeyName(enumerator.Current.Key, ref state, options);
- writer.WritePropertyName(key);
+ TKey key = enumerator.Current.Key;
+ keyConverter.WriteWithQuotes(writer, key, options, ref state);
}
TValue element = enumerator.Current.Value;
- if (!converter.TryWrite(writer, element, options, ref state))
+ if (!valueConverter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
{
if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface)
{
- return typeof(Dictionary<string, TValue>);
+ return typeof(Dictionary<TKey, TValue>);
}
return TypeToConvert;
[DynamicDependency("#ctor", typeof(ArrayConverter<,>))]
[DynamicDependency("#ctor", typeof(ConcurrentQueueOfTConverter<,>))]
[DynamicDependency("#ctor", typeof(ConcurrentStackOfTConverter<,>))]
- [DynamicDependency("#ctor", typeof(DictionaryOfStringTValueConverter<,>))]
+ [DynamicDependency("#ctor", typeof(DictionaryOfTKeyTValueConverter<,,>))]
[DynamicDependency("#ctor", typeof(ICollectionOfTConverter<,>))]
- [DynamicDependency("#ctor", typeof(IDictionaryOfStringTValueConverter<,>))]
+ [DynamicDependency("#ctor", typeof(IDictionaryOfTKeyTValueConverter<,,>))]
[DynamicDependency("#ctor", typeof(IEnumerableOfTConverter<,>))]
[DynamicDependency("#ctor", typeof(IEnumerableWithAddMethodConverter<>))]
[DynamicDependency("#ctor", typeof(IListConverter<>))]
[DynamicDependency("#ctor", typeof(IListOfTConverter<,>))]
- [DynamicDependency("#ctor", typeof(ImmutableDictionaryOfStringTValueConverter<,>))]
+ [DynamicDependency("#ctor", typeof(ImmutableDictionaryOfTKeyTValueConverter<,,>))]
[DynamicDependency("#ctor", typeof(ImmutableEnumerableOfTConverter<,>))]
- [DynamicDependency("#ctor", typeof(IReadOnlyDictionaryOfStringTValueConverter<,>))]
+ [DynamicDependency("#ctor", typeof(IReadOnlyDictionaryOfTKeyTValueConverter<,,>))]
[DynamicDependency("#ctor", typeof(ISetOfTConverter<,>))]
[DynamicDependency("#ctor", typeof(ListOfTConverter<,>))]
[DynamicDependency("#ctor", typeof(QueueOfTConverter<,>))]
[DynamicDependency("#ctor", typeof(StackOfTConverter<,>))]
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
- Type converterType = null!;
+ Type converterType;
Type[] genericArgs;
Type? elementType = null;
+ Type? dictionaryKeyType = null;
Type? actualTypeToConvert;
// Array
converterType = typeof(ListOfTConverter<,>);
elementType = actualTypeToConvert.GetGenericArguments()[0];
}
- // Dictionary<string,> or deriving from Dictionary<string,>
+ // Dictionary<TKey, TValue> or deriving from Dictionary<TKey, TValue>
else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericBaseClass(typeof(Dictionary<,>))) != null)
{
genericArgs = actualTypeToConvert.GetGenericArguments();
- if (genericArgs[0] == typeof(string))
- {
- converterType = typeof(DictionaryOfStringTValueConverter<,>);
- elementType = genericArgs[1];
- }
- else
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(typeToConvert);
- }
+ converterType = typeof(DictionaryOfTKeyTValueConverter<,,>);
+ dictionaryKeyType = genericArgs[0];
+ elementType = genericArgs[1];
}
- // Immutable dictionaries from System.Collections.Immutable, e.g. ImmutableDictionary<string, TValue>
+ // Immutable dictionaries from System.Collections.Immutable, e.g. ImmutableDictionary<TKey, TValue>
else if (typeToConvert.IsImmutableDictionaryType())
{
genericArgs = typeToConvert.GetGenericArguments();
- if (genericArgs[0] == typeof(string))
- {
- converterType = typeof(ImmutableDictionaryOfStringTValueConverter<,>);
- elementType = genericArgs[1];
- }
- else
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(typeToConvert);
- }
+ converterType = typeof(ImmutableDictionaryOfTKeyTValueConverter<,,>);
+ dictionaryKeyType = genericArgs[0];
+ elementType = genericArgs[1];
}
- // IDictionary<string,> or deriving from IDictionary<string,>
+ // IDictionary<TKey, TValue> or deriving from IDictionary<TKey, TValue>
else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IDictionary<,>))) != null)
{
genericArgs = actualTypeToConvert.GetGenericArguments();
- if (genericArgs[0] == typeof(string))
- {
- converterType = typeof(IDictionaryOfStringTValueConverter<,>);
- elementType = genericArgs[1];
- }
- else
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(typeToConvert);
- }
+ converterType = typeof(IDictionaryOfTKeyTValueConverter<,,>);
+ dictionaryKeyType = genericArgs[0];
+ elementType = genericArgs[1];
}
- // IReadOnlyDictionary<string,> or deriving from IReadOnlyDictionary<string,>
+ // IReadOnlyDictionary<TKey, TValue> or deriving from IReadOnlyDictionary<TKey, TValue>
else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IReadOnlyDictionary<,>))) != null)
{
genericArgs = actualTypeToConvert.GetGenericArguments();
- if (genericArgs[0] == typeof(string))
- {
- converterType = typeof(IReadOnlyDictionaryOfStringTValueConverter<,>);
- elementType = genericArgs[1];
- }
- else
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(typeToConvert);
- }
+ converterType = typeof(IReadOnlyDictionaryOfTKeyTValueConverter<,,>);
+ dictionaryKeyType = genericArgs[0];
+ elementType = genericArgs[1];
}
// Immutable non-dictionaries from System.Collections.Immutable, e.g. ImmutableStack<T>
else if (typeToConvert.IsImmutableEnumerableType())
converterType = typeof(IEnumerableConverter<>);
}
- Debug.Assert(converterType != null);
-
Type genericType;
- if (converterType.GetGenericArguments().Length == 1)
+ int numberOfGenericArgs = converterType.GetGenericArguments().Length;
+ if (numberOfGenericArgs == 1)
{
genericType = converterType.MakeGenericType(typeToConvert);
}
- else
+ else if (numberOfGenericArgs == 2)
{
genericType = converterType.MakeGenericType(typeToConvert, elementType!);
}
+ else
+ {
+ Debug.Assert(numberOfGenericArgs == 3);
+ genericType = converterType.MakeGenericType(typeToConvert, dictionaryKeyType!, elementType!);
+ }
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
genericType,
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class IReadOnlyDictionaryOfStringTValueConverter<TCollection, TValue>
- : DictionaryDefaultConverter<TCollection, TValue>
- where TCollection : IReadOnlyDictionary<string, TValue>
+ internal sealed class IReadOnlyDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>
+ : DictionaryDefaultConverter<TCollection, TKey, TValue>
+ where TCollection : IReadOnlyDictionary<TKey, TValue>
+ where TKey : notnull
{
- 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)
{
- string key = state.Current.JsonPropertyNameAsString!;
- ((Dictionary<string, TValue>)state.Current.ReturnValue!)[key] = value;
+ ((Dictionary<TKey, TValue>)state.Current.ReturnValue!)[key] = value;
}
protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state)
protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
{
- IEnumerator<KeyValuePair<string, TValue>> enumerator;
+ IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
}
else
{
- enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
+ enumerator = (Dictionary<TKey, TValue>.Enumerator)state.Current.CollectionEnumerator;
}
- JsonConverter<TValue> converter = GetValueConverter(ref state);
+ JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
do
{
if (ShouldFlush(writer, ref state))
if (state.Current.PropertyState < StackFramePropertyState.Name)
{
state.Current.PropertyState = StackFramePropertyState.Name;
- string key = GetKeyName(enumerator.Current.Key, ref state, options);
- writer.WritePropertyName(key);
+
+ TKey key = enumerator.Current.Key;
+ keyConverter.WriteWithQuotes(writer, key, options, ref state);
}
TValue element = enumerator.Current.Value;
- if (!converter.TryWrite(writer, element, options, ref state))
+ if (!valueConverter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
return true;
}
- internal override Type RuntimeType => typeof(Dictionary<string, TValue>);
+ internal override Type RuntimeType => typeof(Dictionary<TKey, TValue>);
}
}
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class ImmutableDictionaryOfStringTValueConverter<TCollection, TValue>
- : DictionaryDefaultConverter<TCollection, TValue>
- where TCollection : IReadOnlyDictionary<string, TValue>
+ internal sealed class ImmutableDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>
+ : DictionaryDefaultConverter<TCollection, TKey, TValue>
+ where TCollection : IReadOnlyDictionary<TKey, TValue>
+ where TKey : notnull
{
- 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)
{
- string key = state.Current.JsonPropertyNameAsString!;
- ((Dictionary<string, TValue>)state.Current.ReturnValue!)[key] = value;
+ ((Dictionary<TKey, TValue>)state.Current.ReturnValue!)[key] = value;
}
internal override bool CanHaveIdMetadata => false;
protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
{
- IEnumerator<KeyValuePair<string, TValue>> enumerator;
+ IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
}
else
{
- enumerator = (IEnumerator<KeyValuePair<string, TValue>>)state.Current.CollectionEnumerator;
+ enumerator = (IEnumerator<KeyValuePair<TKey, TValue>>)state.Current.CollectionEnumerator;
}
- JsonConverter<TValue> converter = GetValueConverter(ref state);
+ JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
do
{
if (ShouldFlush(writer, ref state))
if (state.Current.PropertyState < StackFramePropertyState.Name)
{
state.Current.PropertyState = StackFramePropertyState.Name;
- string key = GetKeyName(enumerator.Current.Key, ref state, options);
- writer.WritePropertyName(key);
+
+ TKey key = enumerator.Current.Key;
+ keyConverter.WriteWithQuotes(writer, key, options, ref state);
}
TValue element = enumerator.Current.Value;
- if (!converter.TryWrite(writer, element, options, ref state))
+ if (!valueConverter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Buffers.Text;
+
namespace System.Text.Json.Serialization.Converters
{
internal sealed class BooleanConverter : JsonConverter<bool>
{
writer.WriteBooleanValue(value);
}
+
+ internal override bool ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ ReadOnlySpan<byte> propertyName = reader.GetSpan();
+ if (Utf8Parser.TryParse(propertyName, out bool value, out int bytesConsumed)
+ && propertyName.Length == bytesConsumed)
+ {
+ return value;
+ }
+
+ throw ThrowHelper.GetFormatException(DataType.Boolean);
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, bool value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override byte ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetByteWithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, byte value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
#endif
);
}
+
+ internal override char ReadWithQuotes(ref Utf8JsonReader reader)
+ => Read(ref reader, default!, default!);
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, char value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(
+#if BUILDING_INBOX_LIBRARY
+ MemoryMarshal.CreateSpan(ref value, 1)
+#else
+ value.ToString()
+#endif
+ );
+ }
}
}
{
writer.WriteStringValue(value);
}
+
+ internal override DateTime ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetDateTimeNoValidation();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteStringValue(value);
}
+
+ internal override DateTimeOffset ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetDateTimeOffsetNoValidation();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override decimal ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetDecimalWithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override double ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetDoubleWithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, double value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
return default;
}
- // Try parsing case sensitive first
- string? enumString = reader.GetString();
- if (!Enum.TryParse(enumString, out T value)
- && !Enum.TryParse(enumString, ignoreCase: true, out value))
- {
- ThrowHelper.ThrowJsonException();
- return default;
- }
- return value;
+ return ReadWithQuotes(ref reader);
}
if (token != JsonTokenType.Number || !_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers))
return converted;
}
+
+ internal override T ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ string? enumString = reader.GetString();
+
+ // Try parsing case sensitive first
+ if (!Enum.TryParse(enumString, out T value)
+ && !Enum.TryParse(enumString, ignoreCase: true, out value))
+ {
+ ThrowHelper.ThrowJsonException();
+ }
+
+ return value;
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ // An EnumConverter that invokes this method
+ // can only be created by JsonSerializerOptions.GetDictionaryKeyConverter
+ // hence no naming policy is expected.
+ Debug.Assert(_namingPolicy == null);
+
+ ulong key = ConvertToUInt64(value);
+
+ if (_nameCache.TryGetValue(key, out JsonEncodedText formatted))
+ {
+ writer.WritePropertyName(formatted);
+ return;
+ }
+
+ string original = value.ToString();
+ if (IsValidIdentifier(original))
+ {
+ // We are dealing with a combination of flag constants since
+ // all constant values were cached during warm-up.
+ JavaScriptEncoder? encoder = options.Encoder;
+
+ if (_nameCache.Count < NameCacheSizeSoftLimit)
+ {
+ formatted = JsonEncodedText.Encode(original, encoder);
+
+ writer.WritePropertyName(formatted);
+
+ _nameCache.TryAdd(key, formatted);
+ }
+ else
+ {
+ // We also do not create a JsonEncodedText instance here because passing the string
+ // directly to the writer is cheaper than creating one and not caching it for reuse.
+ writer.WritePropertyName(original);
+ }
+
+ return;
+ }
+
+ switch (s_enumTypeCode)
+ {
+ case TypeCode.Int32:
+ writer.WritePropertyName(Unsafe.As<T, int>(ref value));
+ break;
+ case TypeCode.UInt32:
+ writer.WritePropertyName(Unsafe.As<T, uint>(ref value));
+ break;
+ case TypeCode.UInt64:
+ writer.WritePropertyName(Unsafe.As<T, ulong>(ref value));
+ break;
+ case TypeCode.Int64:
+ writer.WritePropertyName(Unsafe.As<T, long>(ref value));
+ break;
+ case TypeCode.Int16:
+ writer.WritePropertyName(Unsafe.As<T, short>(ref value));
+ break;
+ case TypeCode.UInt16:
+ writer.WritePropertyName(Unsafe.As<T, ushort>(ref value));
+ break;
+ case TypeCode.Byte:
+ writer.WritePropertyName(Unsafe.As<T, byte>(ref value));
+ break;
+ case TypeCode.SByte:
+ writer.WritePropertyName(Unsafe.As<T, sbyte>(ref value));
+ break;
+ default:
+ ThrowHelper.ThrowJsonException();
+ break;
+ }
+ }
}
}
{
writer.WriteStringValue(value);
}
+
+ internal override Guid ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetGuidNoValidation();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Diagnostics.CodeAnalysis;
+
namespace System.Text.Json.Serialization.Converters
{
internal sealed class Int16Converter : JsonConverter<short>
{
writer.WriteNumberValue(value);
}
+
+ internal override short ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetInt16WithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, short value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override int ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetInt32WithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, int value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override long ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetInt64WithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, long value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
throw new InvalidOperationException();
}
+
+ internal override object ReadWithQuotes(ref Utf8JsonReader reader)
+ => throw new NotSupportedException();
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ JsonConverter runtimeConverter = GetRuntimeConverter(value.GetType(), options);
+ runtimeConverter.WriteWithQuotesAsObject(writer, value, options, ref state);
+ }
+
+ private JsonConverter GetRuntimeConverter(Type runtimeType, JsonSerializerOptions options)
+ {
+ JsonConverter runtimeConverter = options.GetDictionaryKeyConverter(runtimeType);
+ if (runtimeConverter == this)
+ {
+ ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(runtimeType);
+ }
+
+ return runtimeConverter;
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override sbyte ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetSByteWithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, sbyte value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override float ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetSingleWithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, float value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Diagnostics.CodeAnalysis;
+
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class StringConverter : JsonConverter<string?>
+ internal sealed class StringConverter : JsonConverter<string>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
{
writer.WriteStringValue(value);
}
+
+ internal override string ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetString()!;
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, string value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ if (options.DictionaryKeyPolicy != null && !state.Current.IgnoreDictionaryKeyPolicy)
+ {
+ value = options.DictionaryKeyPolicy.ConvertName(value);
+
+ if (value == null)
+ {
+ ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(options.DictionaryKeyPolicy);
+ }
+ }
+
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override ushort ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetUInt16WithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override uint ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetUInt32WithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, uint value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
{
writer.WriteNumberValue(value);
}
+
+ internal override ulong ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ return reader.GetUInt64WithQuotes();
+ }
+
+ internal override void WriteWithQuotes(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ writer.WritePropertyName(value);
+ }
}
}
/// </summary>
internal abstract bool WriteCoreAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state);
+ /// <summary>
+ /// Loosely-typed WriteWithQuotes() that forwards to strongly-typed WriteWithQuotes().
+ /// </summary>
+ internal abstract void WriteWithQuotesAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state);
+
// Whether a type (ClassType.Object) is deserialized using a parameterized constructor.
internal virtual bool ConstructorIsParameterized => false;
throw new InvalidOperationException();
}
+
+ internal sealed override void WriteWithQuotesAsObject(
+ Utf8JsonWriter writer, object value,
+ JsonSerializerOptions options,
+ ref WriteStack state)
+ {
+ Debug.Fail("We should never get here.");
+
+ throw new InvalidOperationException();
+ }
}
}
Debug.Assert(this is JsonDictionaryConverter<T>);
- state.Current.PolymorphicJsonPropertyInfo = state.Current.DeclaredJsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PropertyInfoForClassInfo;
-
if (writer.CurrentDepth >= options.EffectiveMaxDepth)
{
ThrowHelper.ThrowJsonException_SerializerCycleDetected(options.EffectiveMaxDepth);
/// <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)
+ => throw new InvalidOperationException();
+
+ internal virtual void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] T value, JsonSerializerOptions options, ref WriteStack state)
+ => throw new InvalidOperationException();
+
+ internal sealed override void WriteWithQuotesAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state)
+ => WriteWithQuotes(writer, (T)value, options, ref state);
}
}
return converters;
void Add(JsonConverter converter) =>
- converters.Add(converter.TypeToConvert!, converter);
+ converters.Add(converter.TypeToConvert, converter);
+ }
+
+ internal JsonConverter GetDictionaryKeyConverter(Type keyType)
+ {
+ _dictionaryKeyConverters ??= GetDictionaryKeyConverters();
+
+ if (!_dictionaryKeyConverters.TryGetValue(keyType, out JsonConverter? converter))
+ {
+ if (keyType.IsEnum)
+ {
+ converter = GetEnumConverter();
+ _dictionaryKeyConverters[keyType] = converter;
+ }
+ else
+ {
+ ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(keyType);
+ }
+ }
+
+ return converter!;
+
+ // Use factory pattern to generate an EnumConverter with AllowStrings and AllowNumbers options for dictionary keys.
+ // There will be one converter created for each enum type.
+ JsonConverter GetEnumConverter()
+ => (JsonConverter)Activator.CreateInstance(
+ typeof(EnumConverter<>).MakeGenericType(keyType),
+ BindingFlags.Instance | BindingFlags.Public,
+ binder: null,
+ new object[] { EnumConverterOptions.AllowStrings | EnumConverterOptions.AllowNumbers, this },
+ culture: null)!;
+ }
+
+ private ConcurrentDictionary<Type, JsonConverter>? _dictionaryKeyConverters;
+
+ private static ConcurrentDictionary<Type, JsonConverter> GetDictionaryKeyConverters()
+ {
+ const int NumberOfConverters = 18;
+ var converters = new ConcurrentDictionary<Type, JsonConverter>(Environment.ProcessorCount, NumberOfConverters);
+
+ // When adding to this, update NumberOfConverters above.
+ Add(s_defaultSimpleConverters[typeof(bool)]);
+ Add(s_defaultSimpleConverters[typeof(byte)]);
+ Add(s_defaultSimpleConverters[typeof(char)]);
+ Add(s_defaultSimpleConverters[typeof(DateTime)]);
+ Add(s_defaultSimpleConverters[typeof(DateTimeOffset)]);
+ Add(s_defaultSimpleConverters[typeof(double)]);
+ Add(s_defaultSimpleConverters[typeof(decimal)]);
+ Add(s_defaultSimpleConverters[typeof(Guid)]);
+ Add(s_defaultSimpleConverters[typeof(short)]);
+ Add(s_defaultSimpleConverters[typeof(int)]);
+ Add(s_defaultSimpleConverters[typeof(long)]);
+ Add(s_defaultSimpleConverters[typeof(object)]);
+ Add(s_defaultSimpleConverters[typeof(sbyte)]);
+ Add(s_defaultSimpleConverters[typeof(float)]);
+ Add(s_defaultSimpleConverters[typeof(string)]);
+ Add(s_defaultSimpleConverters[typeof(ushort)]);
+ Add(s_defaultSimpleConverters[typeof(uint)]);
+ Add(s_defaultSimpleConverters[typeof(ulong)]);
+
+ Debug.Assert(NumberOfConverters == converters.Count);
+
+ return converters;
+
+ void Add(JsonConverter converter) =>
+ converters[converter.TypeToConvert] = converter;
}
/// <summary>
public StackFramePropertyState PropertyState;
public bool UseExtensionProperty;
- // Support JSON Path on exceptions.
- public byte[]? JsonPropertyName; // This is Utf8 since we don't want to convert to string until an exception is thown.
- public string? JsonPropertyNameAsString; // This is used for dictionary keys and re-entry cases that specify a property name.
+ // Support JSON Path on exceptions and non-string Dictionary keys.
+ // This is Utf8 since we don't want to convert to string until an exception is thown.
+ // For dictionary keys we don't want to convert to TKey until we have both key and value when parsing the dictionary elements on stream cases.
+ public byte[]? JsonPropertyName;
+ public string? JsonPropertyNameAsString; // This is used for string dictionary keys and re-entry cases that specify a property name.
+
+ // Stores the non-string dictionary keys for continuation.
+ public object? DictionaryKey;
// Validation state.
public int OriginalDepth;
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowNotSupportedException_DictionaryKeyTypeNotSupported(Type keyType)
+ {
+ throw new NotSupportedException(SR.Format(SR.DictionaryKeyTypeNotSupported, keyType));
+ }
+
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
{
var ex = new JsonException(SR.Format(SR.DeserializeUnableToConvertValue, propertyType));
switch (dateType)
{
+ case DataType.Boolean:
+ message = SR.FormatBoolean;
+ break;
case DataType.DateTime:
message = SR.FormatDateTime;
break;
internal enum DataType
{
+ Boolean,
DateTime,
DateTimeOffset,
Base64String,
output[BytesPending++] = JsonConstants.Quote;
}
+
+ internal void WritePropertyName(DateTime value)
+ {
+ Span<byte> buffer = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+ JsonWriterHelper.WriteDateTimeTrimmed(buffer, value, out int bytesWritten);
+ WritePropertyNameUnescaped(buffer.Slice(0, bytesWritten));
+ }
}
}
output[BytesPending++] = JsonConstants.Quote;
}
+
+ internal void WritePropertyName(DateTimeOffset value)
+ {
+ Span<byte> buffer = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+ JsonWriterHelper.WriteDateTimeOffsetTrimmed(buffer, value, out int bytesWritten);
+ WritePropertyNameUnescaped(buffer.Slice(0, bytesWritten));
+ }
}
}
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(decimal value)
+ {
+ Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatDecimalLength];
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(double value)
+ {
+ JsonWriterHelper.ValidateDouble(value);
+ Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatDoubleLength];
+ bool result = TryFormatDouble(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(float value)
+ {
+ Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatSingleLength];
+ bool result = TryFormatSingle(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
output[BytesPending++] = JsonConstants.Quote;
}
+
+ internal void WritePropertyName(Guid value)
+ {
+ Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatGuidLength];
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
// See the LICENSE file in the project root for more information.
using System.Buffers;
+using System.Buffers.Text;
using System.Diagnostics;
using System.Runtime.CompilerServices;
value.CopyTo(output.Slice(BytesPending));
BytesPending += value.Length;
}
+
+ internal void WritePropertyName(bool value)
+ {
+ Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatBooleanLength];
+
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(int value)
+ => WritePropertyName((long)value);
+
+ internal void WritePropertyName(long value)
+ {
+ Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatInt64Length];
+
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
_tokenType = JsonTokenType.PropertyName;
}
+ private void WritePropertyNameUnescaped(ReadOnlySpan<byte> utf8PropertyName)
+ {
+ JsonWriterHelper.ValidateProperty(utf8PropertyName);
+ WriteStringByOptionsPropertyName(utf8PropertyName);
+
+ _currentDepth &= JsonConstants.RemoveFlagsBitMask;
+ _tokenType = JsonTokenType.PropertyName;
+ }
+
private void WriteStringEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, int firstEscapeIndexProp)
{
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(uint value)
+ => WritePropertyName((ulong)value);
+
+ internal void WritePropertyName(ulong value)
+ {
+ Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatUInt64Length];
+
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+ public partial class DictionaryTests
+ {
+ public abstract class DictionaryKeyTestsBase<TKey, TValue>
+ {
+ protected abstract TKey Key { get; }
+ protected abstract TValue Value { get; }
+ protected virtual string _expectedJson => $"{{\"{Key}\":{Value}}}";
+
+ protected virtual void Validate(Dictionary<TKey, TValue> dictionary)
+ {
+ bool success = dictionary.TryGetValue(Key, out TValue value);
+ Assert.True(success);
+ Assert.Equal(Value, value);
+ }
+
+ private Dictionary<TKey, TValue> BuildDictionary()
+ {
+ var dictionary = new Dictionary<TKey, TValue>();
+ dictionary.Add(Key, Value);
+
+ return dictionary;
+ }
+
+ [Fact]
+ public void TestDictionaryKey()
+ {
+ Dictionary<TKey, TValue> dictionary = BuildDictionary();
+
+ string json = JsonSerializer.Serialize(dictionary);
+ Assert.Equal(_expectedJson, json);
+
+ Dictionary<TKey, TValue> dictionaryCopy = JsonSerializer.Deserialize<Dictionary<TKey, TValue>>(json);
+ Validate(dictionaryCopy);
+ }
+
+ [Fact]
+ public async Task TestDictionaryKeyAsync()
+ {
+ Dictionary<TKey, TValue> dictionary = BuildDictionary();
+
+ MemoryStream serializeStream = new MemoryStream();
+ await JsonSerializer.SerializeAsync(serializeStream, dictionary);
+ string json = Encoding.UTF8.GetString(serializeStream.ToArray());
+ Assert.Equal(_expectedJson, json);
+
+ byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
+ Stream deserializeStream = new MemoryStream(jsonBytes);
+ Dictionary<TKey, TValue> dictionaryCopy = await JsonSerializer.DeserializeAsync<Dictionary<TKey, TValue>>(deserializeStream);
+ Validate(dictionaryCopy);
+ }
+ }
+
+ public class DictionaryBoolKey : DictionaryKeyTestsBase<bool, int>
+ {
+ protected override bool Key => true;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryByteKey : DictionaryKeyTestsBase<byte, int>
+ {
+ protected override byte Key => byte.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryCharKey : DictionaryKeyTestsBase<char, char>
+ {
+ protected override string _expectedJson => @"{""\uFFFF"":""\uFFFF""}";
+ protected override char Key => char.MaxValue;
+ protected override char Value => char.MaxValue;
+ }
+
+ public class DictionaryDateTimeKey : DictionaryKeyTestsBase<DateTime, int>
+ {
+ protected override string _expectedJson => $@"{{""{DateTime.MaxValue:O}"":1}}";
+ protected override DateTime Key => DateTime.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryDateTimeOffsetKey : DictionaryKeyTestsBase<DateTimeOffset, int>
+ {
+ protected override string _expectedJson => $@"{{""{DateTimeOffset.MaxValue:O}"":1}}";
+ protected override DateTimeOffset Key => DateTimeOffset.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryDecimalKey : DictionaryKeyTestsBase<decimal, int>
+ {
+ protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(decimal.MaxValue)}"":1}}";
+ protected override decimal Key => decimal.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryDoubleKey : DictionaryKeyTestsBase<double, int>
+ {
+ protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(double.MaxValue)}"":1}}";
+ protected override double Key => double.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryEnumKey : DictionaryKeyTestsBase<MyEnum, int>
+ {
+ protected override MyEnum Key => MyEnum.Foo;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryEnumFlagsKey : DictionaryKeyTestsBase<MyEnumFlags, int>
+ {
+ protected override MyEnumFlags Key => MyEnumFlags.Foo | MyEnumFlags.Bar;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryGuidKey : DictionaryKeyTestsBase<Guid, int>
+ {
+ // Use singleton pattern here so the Guid key does not change everytime this is called.
+ protected override Guid Key { get; } = Guid.NewGuid();
+ protected override int Value => 1;
+ }
+
+ public class DictionaryInt16Key : DictionaryKeyTestsBase<short, int>
+ {
+ protected override short Key => short.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryInt32Key : DictionaryKeyTestsBase<int, int>
+ {
+ protected override int Key => int.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryInt64Key : DictionaryKeyTestsBase<long, int>
+ {
+ protected override long Key => long.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionarySByteKey : DictionaryKeyTestsBase<sbyte, int>
+ {
+ protected override sbyte Key => sbyte.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionarySingleKey : DictionaryKeyTestsBase<float, int>
+ {
+ protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(float.MaxValue)}"":1}}";
+ protected override float Key => float.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryStringKey : DictionaryKeyTestsBase<string, int>
+ {
+ protected override string Key => "KeyString";
+ protected override int Value => 1;
+ }
+
+ public class DictionaryUInt16Key : DictionaryKeyTestsBase<ushort, int>
+ {
+ protected override ushort Key => ushort.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryUInt32Key : DictionaryKeyTestsBase<uint, int>
+ {
+ protected override uint Key => uint.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryUInt64Key : DictionaryKeyTestsBase<ulong, int>
+ {
+ protected override ulong Key => ulong.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public abstract class DictionaryUnsupportedKeyTestsBase<TKey, TValue>
+ {
+ private Dictionary<TKey, TValue> _dictionary => BuildDictionary();
+ protected abstract TKey Key { get; }
+ private Dictionary<TKey, TValue> BuildDictionary()
+ {
+ return new Dictionary<TKey, TValue>() { { Key, default } };
+ }
+
+ [Fact]
+ public void ThrowUnsupported_Serialize()
+ => Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(_dictionary));
+
+ [Fact]
+ public Task ThrowUnsupported_SerializeAsync()
+ => Assert.ThrowsAsync<NotSupportedException>(() => JsonSerializer.SerializeAsync(new MemoryStream(), _dictionary));
+
+ [Fact]
+ public void ThrowUnsupported_Deserialize() => Assert.Throws<NotSupportedException>(()
+ => JsonSerializer.Deserialize<Dictionary<TKey, TValue>>(@"{""foo"":1}"));
+
+ [Fact]
+ public Task ThrowUnsupported_DeserializeAsync() => Assert.ThrowsAsync<NotSupportedException>(()
+ => JsonSerializer.DeserializeAsync<Dictionary<TKey, TValue>>(new MemoryStream(Encoding.UTF8.GetBytes(@"{""foo"":1}"))).AsTask());
+
+ [Fact]
+ public void DoesNotThrowIfEmpty_Serialize()
+ => JsonSerializer.Serialize(new Dictionary<TKey, TValue>());
+
+ [Fact]
+ public Task DoesNotThrowIfEmpty_SerializeAsync()
+ => JsonSerializer.SerializeAsync(new MemoryStream(), new Dictionary<TKey, TValue>());
+
+ [Fact]
+ public void DoesNotThrowIfEmpty_Deserialize()
+ => JsonSerializer.Deserialize<Dictionary<TKey, TValue>>("{}");
+
+ [Fact]
+ public Task DoesNotThrowIfEmpty_DeserializeAsync()
+ => JsonSerializer.DeserializeAsync<Dictionary<TKey, TValue>>(new MemoryStream(Encoding.UTF8.GetBytes("{}"))).AsTask();
+ }
+
+ public class DictionaryMyPublicClassKeyUnsupported : DictionaryUnsupportedKeyTestsBase<MyPublicClass, int>
+ {
+ protected override MyPublicClass Key => new MyPublicClass();
+ }
+
+ public class DictionaryMyPublicStructKeyUnsupported : DictionaryUnsupportedKeyTestsBase<MyPublicStruct, int>
+ {
+ protected override MyPublicStruct Key => new MyPublicStruct();
+ }
+
+ public class DictionaryUriKeyUnsupported : DictionaryUnsupportedKeyTestsBase<Uri, int>
+ {
+ protected override Uri Key => new Uri("http://foo");
+ }
+
+ public class DictionaryObjectKeyUnsupported : DictionaryUnsupportedKeyTestsBase<object, int>
+ {
+ protected override object Key => new object();
+ }
+
+ public class DictionaryPolymorphicKeyUnsupported : DictionaryUnsupportedKeyTestsBase<object, int>
+ {
+ protected override object Key => new Uri("http://foo");
+ }
+
+ public class DictionaryNonStringKeyTests
+ {
+ [Fact]
+ public void TestGenericDictionaryKeyObject()
+ {
+ var dictionary = new Dictionary<object, object>();
+ // Add multiple supported types.
+ dictionary.Add(1, 1);
+ dictionary.Add(new Guid("08314FA2-B1FE-4792-BCD1-6E62338AC7F3"), 2);
+ dictionary.Add("KeyString", 3);
+ dictionary.Add(MyEnum.Foo, 4);
+ dictionary.Add(MyEnumFlags.Foo | MyEnumFlags.Bar, 5);
+
+ const string expected = @"{""1"":1,""08314fa2-b1fe-4792-bcd1-6e62338ac7f3"":2,""KeyString"":3,""Foo"":4,""Foo, Bar"":5}";
+
+ string json = JsonSerializer.Serialize(dictionary);
+ Assert.Equal(expected, json);
+ // object type is not supported on deserialization.
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<Dictionary<object, object>>(json));
+
+ var @object = new ClassWithDictionary { Dictionary = dictionary };
+ json = JsonSerializer.Serialize(@object);
+ Assert.Equal($@"{{""Dictionary"":{expected}}}", json);
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithDictionary>(json));
+ }
+
+ [Fact]
+ public void TestNonGenericDictionaryKeyObject()
+ {
+ IDictionary dictionary = new OrderedDictionary();
+ // Add multiple supported types.
+ dictionary.Add(1, 1);
+ dictionary.Add(new Guid("08314FA2-B1FE-4792-BCD1-6E62338AC7F3"), 2);
+ dictionary.Add("KeyString", 3);
+ dictionary.Add(MyEnum.Foo, 4);
+ dictionary.Add(MyEnumFlags.Foo | MyEnumFlags.Bar, 5);
+
+ const string expected = @"{""1"":1,""08314fa2-b1fe-4792-bcd1-6e62338ac7f3"":2,""KeyString"":3,""Foo"":4,""Foo, Bar"":5}";
+ string json = JsonSerializer.Serialize(dictionary);
+ Assert.Equal(expected, json);
+
+ dictionary = JsonSerializer.Deserialize<IDictionary>(json);
+ Assert.IsType<Dictionary<string, object>>(dictionary);
+
+ dictionary = JsonSerializer.Deserialize<OrderedDictionary>(json);
+ foreach (object key in dictionary.Keys)
+ {
+ Assert.IsType<string>(key);
+ }
+
+ var @object = new ClassWithIDictionary { Dictionary = dictionary };
+ json = JsonSerializer.Serialize(@object);
+ Assert.Equal($@"{{""Dictionary"":{expected}}}", json);
+
+ @object = JsonSerializer.Deserialize<ClassWithIDictionary>(json);
+ Assert.IsType<Dictionary<string, object>>(@object.Dictionary);
+ }
+
+ [Theory] // Extend this test when support for more types is added.
+ [InlineData(@"{""1.1"":1}", typeof(Dictionary<int, int>))]
+ [InlineData(@"{""{00000000-0000-0000-0000-000000000000}"":1}", typeof(Dictionary<Guid, int>))]
+ public void ThrowOnInvalidFormat(string json, Type typeToConvert)
+ {
+ JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize(json, typeToConvert));
+ Assert.Contains(typeToConvert.ToString(), ex.Message);
+ }
+
+ [Theory] // Extend this test when support for more types is added.
+ [InlineData(@"{""1.1"":1}", typeof(Dictionary<int, int>))]
+ [InlineData(@"{""{00000000-0000-0000-0000-000000000000}"":1}", typeof(Dictionary<Guid, int>))]
+ public async Task ThrowOnInvalidFormatAsync(string json, Type typeToConvert)
+ {
+ byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
+ Stream stream = new MemoryStream(jsonBytes);
+
+ JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await JsonSerializer.DeserializeAsync(stream, typeToConvert));
+ Assert.Contains(typeToConvert.ToString(), ex.Message);
+ }
+
+ [Fact]
+ public static void TestNotSuportedExceptionIsThrown()
+ {
+ // Dictionary<int[], int>>
+ Assert.Null(JsonSerializer.Deserialize<Dictionary<int[], int>>("null"));
+ Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Dictionary<int[], int>>("\"\""));
+ Assert.NotNull(JsonSerializer.Deserialize<Dictionary<int[], int>>("{}"));
+
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<Dictionary<int[], int>>(@"{""Foo"":1}"));
+
+ // UnsupportedDictionaryWrapper
+ Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<UnsupportedDictionaryWrapper>("\"\""));
+ Assert.NotNull(JsonSerializer.Deserialize<UnsupportedDictionaryWrapper>("{}"));
+ Assert.Null(JsonSerializer.Deserialize<UnsupportedDictionaryWrapper>("null"));
+ Assert.NotNull(JsonSerializer.Deserialize<UnsupportedDictionaryWrapper>(@"{""Dictionary"":null}"));
+ Assert.NotNull(JsonSerializer.Deserialize<UnsupportedDictionaryWrapper>(@"{""Dictionary"":{}}"));
+
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<UnsupportedDictionaryWrapper>(@"{""Dictionary"":{""Foo"":1}}"));
+ }
+
+ [Fact]
+ public void TestPolicyOnlyAppliesToString()
+ {
+ var opts = new JsonSerializerOptions
+ {
+ DictionaryKeyPolicy = new FixedNamingPolicy()
+ };
+
+ var stringIntDictionary = new Dictionary<string, int> { { "1", 1 } };
+ string json = JsonSerializer.Serialize(stringIntDictionary, opts);
+ Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json);
+
+ var intIntDictionary = new Dictionary<int, int> { { 1, 1 } };
+ json = JsonSerializer.Serialize(intIntDictionary, opts);
+ Assert.Equal(@"{""1"":1}", json);
+
+ var objectIntDictionary = new Dictionary<object, int> { { "1", 1 } };
+ json = JsonSerializer.Serialize(objectIntDictionary, opts);
+ Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json);
+
+ objectIntDictionary = new Dictionary<object, int> { { 1, 1 } };
+ json = JsonSerializer.Serialize(objectIntDictionary, opts);
+ Assert.Equal(@"{""1"":1}", json);
+ }
+
+ [Fact]
+ public async Task TestPolicyOnlyAppliesToStringAsync()
+ {
+ var opts = new JsonSerializerOptions
+ {
+ DictionaryKeyPolicy = new FixedNamingPolicy()
+ };
+
+ MemoryStream stream = new MemoryStream();
+
+ var stringIntDictionary = new Dictionary<string, int> { { "1", 1 } };
+ await JsonSerializer.SerializeAsync(stream, stringIntDictionary, opts);
+
+ string json = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json);
+
+ stream.Position = 0;
+ stream.SetLength(0);
+
+ var intIntDictionary = new Dictionary<int, int> { { 1, 1 } };
+ await JsonSerializer.SerializeAsync(stream, intIntDictionary, opts);
+
+ json = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal(@"{""1"":1}", json);
+
+ stream.Position = 0;
+ stream.SetLength(0);
+
+ var objectIntDictionary = new Dictionary<object, int> { { "1", 1 } };
+ await JsonSerializer.SerializeAsync(stream, objectIntDictionary, opts);
+
+ json = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json);
+
+ stream.Position = 0;
+ stream.SetLength(0);
+
+ objectIntDictionary = new Dictionary<object, int> { { 1, 1 } };
+ await JsonSerializer.SerializeAsync(stream, objectIntDictionary, opts);
+
+ json = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal(@"{""1"":1}", json);
+ }
+
+ [Fact]
+ public void TestEnumKeyWithNotValidIdentifier()
+ {
+ var myEnumIntDictionary = new Dictionary<MyEnum, int>();
+ myEnumIntDictionary.Add((MyEnum)(-1), 1);
+
+ string json = JsonSerializer.Serialize(myEnumIntDictionary);
+ Assert.Equal(@"{""-1"":1}", json);
+
+ myEnumIntDictionary = JsonSerializer.Deserialize<Dictionary<MyEnum, int>>(json);
+ Assert.Equal(1, myEnumIntDictionary[(MyEnum)(-1)]);
+
+ var myEnumFlagsIntDictionary = new Dictionary<MyEnumFlags, int>();
+ myEnumFlagsIntDictionary.Add((MyEnumFlags)(-1), 1);
+
+ json = JsonSerializer.Serialize(myEnumFlagsIntDictionary);
+ Assert.Equal(@"{""-1"":1}", json);
+
+ myEnumFlagsIntDictionary = JsonSerializer.Deserialize<Dictionary<MyEnumFlags, int>>(json);
+ Assert.Equal(1, myEnumFlagsIntDictionary[(MyEnumFlags)(-1)]);
+ }
+
+ [Theory]
+ [MemberData(nameof(DictionaryKeysWithSpecialCharacters))]
+ public void EnsureNonStringKeysDontGetEscapedOnSerialize(object key, string expectedKeySerialized)
+ {
+ Dictionary<object, int> root = new Dictionary<object, int>();
+ root.Add(key, 1);
+
+ string json = JsonSerializer.Serialize(root);
+ Assert.Contains(expectedKeySerialized, json);
+ }
+
+ public static IEnumerable<object[]> DictionaryKeysWithSpecialCharacters =>
+ new List<object[]>
+ {
+ new object[] { float.MaxValue, JsonSerializer.Serialize(float.MaxValue) },
+ new object[] { double.MaxValue, JsonSerializer.Serialize(double.MaxValue) },
+ new object[] { DateTimeOffset.MaxValue, JsonSerializer.Serialize(DateTimeOffset.MaxValue) }
+ };
+
+ [Theory]
+ [MemberData(nameof(EscapedMemberData))]
+ public void TestEscapedValuesOnDeserialize(string escapedPropertyName, object expectedDictionaryKey, Type dictionaryType)
+ {
+ string json = $@"{{""{escapedPropertyName}"":1}}";
+ IDictionary root = (IDictionary)JsonSerializer.Deserialize(json, dictionaryType);
+
+ bool containsKey = root.Contains(expectedDictionaryKey);
+ Assert.True(containsKey);
+ Assert.Equal(1, root[expectedDictionaryKey]);
+ }
+
+ [Theory]
+ [MemberData(nameof(EscapedMemberData))]
+ public async Task TestEscapedValuesOnDeserializeAsync(string escapedPropertyName, object expectedDictionaryKey, Type dictionaryType)
+ {
+ string json = $@"{{""{escapedPropertyName}"":1}}";
+ MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
+ IDictionary root = (IDictionary)await JsonSerializer.DeserializeAsync(stream, dictionaryType);
+
+ bool containsKey = root.Contains(expectedDictionaryKey);
+ Assert.True(containsKey);
+ Assert.Equal(1, root[expectedDictionaryKey]);
+ }
+
+ public static IEnumerable<object[]> EscapedMemberData =>
+ new List<object[]>
+ {
+ new object[] { @"\u0031\u0032\u0037",
+ sbyte.MaxValue, typeof(Dictionary<sbyte, int>) },
+ new object[] { @"\u0032\u0035\u0035",
+ byte.MaxValue, typeof(Dictionary<byte, int>) },
+ new object[] { @"\u0033\u0032\u0037\u0036\u0037",
+ short.MaxValue, typeof(Dictionary<short, int>) },
+ new object[] { @"\u0036\u0035\u0035\u0033\u0035",
+ ushort.MaxValue, typeof(Dictionary<ushort, int>) },
+ new object[] { @"\u0032\u0031\u0034\u0037\u0034\u0038\u0033\u0036\u0034\u0037",
+ int.MaxValue, typeof(Dictionary<int, int>) },
+ new object[] { @"\u0034\u0032\u0039\u0034\u0039\u0036\u0037\u0032\u0039\u0035",
+ uint.MaxValue, typeof(Dictionary<uint, int>) },
+ new object[] { @"\u0039\u0032\u0032\u0033\u0033\u0037\u0032\u0030\u0033\u0036\u0038\u0035\u0034\u0037\u0037\u0035\u0038\u0030\u0037",
+ long.MaxValue, typeof(Dictionary<long, int>) },
+ new object[] { @"\u0031\u0038\u0034\u0034\u0036\u0037\u0034\u0034\u0030\u0037\u0033\u0037\u0030\u0039\u0035\u0035\u0031\u0036\u0031\u0035",
+ ulong.MaxValue, typeof(Dictionary<ulong, int>) },
+ // Do not use max values on floating point types since it may have different string representations depending on the tfm.
+ new object[] { @"\u0033\u002e\u0031\u0032\u0035\u0065\u0037",
+ 3.125e7f, typeof(Dictionary<float, int>) },
+ new object[] { @"\u0033\u002e\u0031\u0032\u0035\u0065\u0037",
+ 3.125e7d, typeof(Dictionary<double, int>) },
+ new object[] { @"\u0033\u002e\u0031\u0032\u0035\u0065\u0037",
+ 3.125e7m, typeof(Dictionary<decimal, int>) },
+ new object[] { @"\u0039\u0039\u0039\u0039\u002d\u0031\u0032\u002d\u0033\u0031\u0054\u0032\u0033\u003a\u0035\u0039\u003a\u0035\u0039\u002e\u0039\u0039\u0039\u0039\u0039\u0039\u0039",
+ DateTime.MaxValue, typeof(Dictionary<DateTime, int>) },
+ new object[] { @"\u0039\u0039\u0039\u0039\u002d\u0031\u0032\u002d\u0033\u0031\u0054\u0032\u0033\u003a\u0035\u0039\u003a\u0035\u0039\u002e\u0039\u0039\u0039\u0039\u0039\u0039\u0039\u002b\u0030\u0030\u003a\u0030\u0030",
+ DateTimeOffset.MaxValue, typeof(Dictionary<DateTimeOffset, int>) },
+ new object[] { @"\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u002d\u0030\u0030\u0030\u0030\u002d\u0030\u0030\u0030\u0030\u002d\u0030\u0030\u0030\u0030\u002d\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030",
+ Guid.Empty, typeof(Dictionary<Guid, int>) },
+ new object[] { @"\u0042\u0061\u0072",
+ MyEnum.Bar, typeof(Dictionary<MyEnum, int>) },
+ new object[] { @"\u0042\u0061\u0072\u002c\u0042\u0061\u007a",
+ MyEnumFlags.Bar | MyEnumFlags.Baz, typeof(Dictionary<MyEnumFlags, int>) },
+ new object[] { @"\u002b", '+', typeof(Dictionary<char, int>) }
+ };
+ }
+
+ public class MyPublicClass { }
+
+ public struct MyPublicStruct { }
+
+ public enum MyEnum
+ {
+ Foo,
+ Bar
+ }
+
+ [Flags]
+ public enum MyEnumFlags
+ {
+ Foo = 1,
+ Bar = 2,
+ Baz = 4
+ }
+
+ private class ClassWithIDictionary
+ {
+ public IDictionary Dictionary { get; set; }
+ }
+
+ private class ClassWithDictionary
+ {
+ public Dictionary<object, object> Dictionary { get; set; }
+ }
+
+ private class ClassWithExtensionData
+ {
+ [JsonExtensionData]
+ public Dictionary<int, object> Overflow { get; set; }
+ }
+
+ private class UnsupportedDictionaryWrapper
+ {
+ public Dictionary<int[], int> Dictionary { get; set; }
+ }
+
+ public class FixedNamingPolicy : JsonNamingPolicy
+ {
+ public const string FixedName = nameof(FixedName);
+ public override string ConvertName(string name) => FixedName;
+ }
+
+ public class SuffixNamingPolicy : JsonNamingPolicy
+ {
+ public const string Suffix = "_Suffix";
+ public override string ConvertName(string name) => name + Suffix;
+ }
+ }
+}
}
[Fact]
- public static void FirstGenericArgNotStringFail()
- {
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<Dictionary<int, int>>(@"{1:1}"));
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ImmutableDictionary<int, int>>(@"{1:1}"));
- }
-
- [Fact]
public static void DictionaryOfList()
{
const string JsonString = @"{""Key1"":[1,2],""Key2"":[3,4]}";
NotSupportedException ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithNotSupportedDictionary>(json));
// The exception contains the type.
- Assert.Contains(typeof(Dictionary<int, int>).ToString(), ex.Message);
- Assert.DoesNotContain("Path: ", ex.Message);
+ Assert.Contains(typeof(Dictionary<int[,], int>).ToString(), ex.Message);
}
[Fact]
public class ClassWithNotSupportedDictionary
{
- public Dictionary<int, int> MyDictionary { get; set; }
+ public Dictionary<int[,], int> MyDictionary { get; set; }
}
public class ClassWithNotSupportedDictionaryButIgnored
{
- [JsonIgnore] public Dictionary<int, int> MyDictionary { get; set; }
+ [JsonIgnore] public Dictionary<int[,], int> MyDictionary { get; set; }
}
public class AllSingleUpperPropertiesParent
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(dictionary));
}
-
- [Fact]
- public static void VerifyIDictionaryWithNonStringKey()
- {
- IDictionary dictionary = new Hashtable();
- dictionary.Add(1, "value");
- Assert.Throws<JsonException>(() => JsonSerializer.Serialize(dictionary));
- }
-
private class ClassWithoutParameterlessCtor
{
public ClassWithoutParameterlessCtor(int num) { }
}
[Fact]
- public static void IReadOnlyDictionary_NonStringKey_NotSupported()
+ public static void IReadOnlyDictionary_NotSupportedKey()
{
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<IReadOnlyDictionary<int, int>>(""));
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new GenericIReadOnlyDictionaryWrapper<int, int>()));
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<IReadOnlyDictionary<Uri, int>>(@"{""http://foo"":1}"));
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new GenericIReadOnlyDictionaryWrapper<Uri, int>(new Dictionary<Uri, int> { { new Uri("http://foo"), 1 } })));
}
}
}
{
DictionaryWrapper = new UnsupportedDictionaryWrapper()
};
- wrapper.DictionaryWrapper[1] = 1;
+ wrapper.DictionaryWrapper[new int[,] { }] = 1;
// Without converter, we throw.
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<UnsupportedDerivedTypesWrapper_Dictionary>(json));
public class DictionaryWrapper : Dictionary<string, int> { }
- public class UnsupportedDictionaryWrapper : Dictionary<int, int> { }
+ public class UnsupportedDictionaryWrapper : Dictionary<int[,], int> { }
public class DerivedTypesWrapper
{
ClassWithInvalidExtensionPropertyStringString obj1 = new ClassWithInvalidExtensionPropertyStringString();
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj1));
- // This fails with NotSupportedException since all Dictionaries currently need to have a string TKey.
ClassWithInvalidExtensionPropertyObjectString obj2 = new ClassWithInvalidExtensionPropertyObjectString();
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(obj2));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj2));
}
private class ClassWithExtensionPropertyAlreadyInstantiated
JsonSerializerOptions options = new JsonSerializerOptions();
// Unsupported collections will throw on serialize by default.
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options));
+ // Only when the collection contains elements.
- // Unsupported collections will throw on deserialize by default.
+ var dictionary = new Dictionary<object, object>();
+ // Uri is an unsupported dictionary key.
+ dictionary.Add(new Uri("http://foo"), "bar");
+
+ var concurrentDictionary = new ConcurrentDictionary<object, object>(dictionary);
+
+ var instance = new ClassWithUnsupportedDictionary()
+ {
+ MyConcurrentDict = concurrentDictionary,
+ MyIDict = dictionary
+ };
+
+ var instanceWithIgnore = new ClassWithIgnoredUnsupportedDictionary
+ {
+ MyConcurrentDict = concurrentDictionary,
+ MyIDict = dictionary
+ };
+
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(instance, options));
+
+ // Unsupported collections will throw on deserialize by default if they contain elements.
options = new JsonSerializerOptions();
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<WrapperForClassWithUnsupportedDictionary>(wrapperJson, options));
options = new JsonSerializerOptions();
- // Unsupported collections will throw on serialize by default.
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options));
+ // Unsupported collections will throw on serialize by default if they contain elements.
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(instance, options));
// When ignored, we can serialize and deserialize without exceptions.
options = new JsonSerializerOptions();
+
+ Assert.NotNull(JsonSerializer.Serialize(instanceWithIgnore, options));
+
ClassWithIgnoredUnsupportedDictionary obj = JsonSerializer.Deserialize<ClassWithIgnoredUnsupportedDictionary>(json, options);
Assert.Null(obj.MyDict);
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkCurrent)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Compile Include="Serialization\CollectionTests\CollectionTests.Concurrent.cs" />
<Compile Include="Serialization\CollectionTests\CollectionTests.Dictionary.cs" />
<Compile Include="Serialization\CollectionTests\CollectionTests.Dictionary.KeyPolicy.cs" />
+ <Compile Include="Serialization\CollectionTests\CollectionTests.Dictionary.NonStringKey.cs" />
<Compile Include="Serialization\CollectionTests\CollectionTests.Generic.Read.cs" />
<Compile Include="Serialization\CollectionTests\CollectionTests.Generic.Write.cs" />
<Compile Include="Serialization\CollectionTests\CollectionTests.Immutable.Read.cs" />