diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx
index 1a029bf64b235..7309f577181ed 100644
--- a/src/libraries/System.Text.Json/src/Resources/Strings.resx
+++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx
@@ -527,4 +527,10 @@
The value cannot be 'JsonIgnoreCondition.Always'.
+
+ The JSON value is not in a supported Boolean format.
+
+
+ The type '{0}' is not a supported Dictionary key type.
+
diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
index f09a442249bba..5d8e706a2aa4b 100644
--- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj
+++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
@@ -66,10 +66,10 @@
-
+
-
+
@@ -78,9 +78,9 @@
-
+
-
+
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs
index 1d13e73cdda8d..0e695e0435372 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs
@@ -66,6 +66,7 @@ internal static class JsonConstants
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.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs
index 642d4a6db5f1b..a81a971a24234 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs
@@ -6,6 +6,7 @@
using System.Buffers.Text;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
namespace System.Text.Json
{
@@ -138,6 +139,16 @@ public byte GetByte()
return value;
}
+ internal byte GetByteWithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetByteCore(out byte value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Byte);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as an .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to an
@@ -163,6 +174,16 @@ public sbyte GetSByte()
return value;
}
+ internal sbyte GetSByteWithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetSByteCore(out sbyte value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.SByte);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -187,6 +208,16 @@ public short GetInt16()
return value;
}
+ internal short GetInt16WithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetInt16Core(out short value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Int16);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as an .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to an
@@ -211,6 +242,16 @@ public int GetInt32()
return value;
}
+ internal int GetInt32WithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetInt32Core(out int value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Int32);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -235,6 +276,16 @@ public long GetInt64()
return value;
}
+ internal long GetInt64WithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetInt64Core(out long value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Int64);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -260,6 +311,16 @@ public ushort GetUInt16()
return value;
}
+ internal ushort GetUInt16WithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetUInt16Core(out ushort value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.UInt16);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -285,6 +346,16 @@ public uint GetUInt32()
return value;
}
+ internal uint GetUInt32WithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetUInt32Core(out uint value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.UInt32);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -310,6 +381,16 @@ public ulong GetUInt64()
return value;
}
+ internal ulong GetUInt64WithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetUInt64Core(out ulong value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.UInt64);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -333,6 +414,16 @@ public float GetSingle()
return value;
}
+ internal float GetSingleWithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetSingleCore(out float value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Single);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -356,6 +447,16 @@ public double GetDouble()
return value;
}
+ internal double GetDoubleWithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetDoubleCore(out double value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Double);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -379,6 +480,16 @@ public decimal GetDecimal()
return value;
}
+ internal decimal GetDecimalWithQuotes()
+ {
+ ReadOnlySpan span = GetUnescapedSpan();
+ if (!TryGetDecimalCore(out decimal value, span))
+ {
+ throw ThrowHelper.GetFormatException(NumericType.Decimal);
+ }
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -402,6 +513,16 @@ public DateTime GetDateTime()
return value;
}
+ internal DateTime GetDateTimeNoValidation()
+ {
+ if (!TryGetDateTimeCore(out DateTime value))
+ {
+ throw ThrowHelper.GetFormatException(DataType.DateTime);
+ }
+
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -425,6 +546,16 @@ public DateTimeOffset GetDateTimeOffset()
return value;
}
+ internal DateTimeOffset GetDateTimeOffsetNoValidation()
+ {
+ if (!TryGetDateTimeOffsetCore(out DateTimeOffset value))
+ {
+ throw ThrowHelper.GetFormatException(DataType.DateTimeOffset);
+ }
+
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source as a .
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a
@@ -448,6 +579,16 @@ public Guid GetGuid()
return value;
}
+ internal Guid GetGuidNoValidation()
+ {
+ if (!TryGetGuidCore(out Guid value))
+ {
+ throw ThrowHelper.GetFormatException(DataType.Guid);
+ }
+
+ return value;
+ }
+
///
/// Parses the current JSON token value from the source and decodes the Base64 encoded JSON string as bytes.
/// Returns if the entire token value is encoded as valid Base64 text and can be successfully
@@ -496,6 +637,12 @@ public bool TryGetByte(out byte value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetByteCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetByteCore(out byte value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out byte tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
@@ -526,6 +673,12 @@ public bool TryGetSByte(out sbyte value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetSByteCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetSByteCore(out sbyte value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out sbyte tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
@@ -555,6 +708,12 @@ public bool TryGetInt16(out short value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetInt16Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetInt16Core(out short value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out short tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
@@ -584,6 +743,12 @@ public bool TryGetInt32(out int value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetInt32Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetInt32Core(out int value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
@@ -613,6 +778,12 @@ public bool TryGetInt64(out long value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetInt64Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetInt64Core(out long value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out long tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
@@ -643,6 +814,12 @@ public bool TryGetUInt16(out ushort value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetUInt16Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetUInt16Core(out ushort value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out ushort tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
@@ -673,6 +850,12 @@ public bool TryGetUInt32(out uint value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetUInt32Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetUInt32Core(out uint value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out uint tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
@@ -703,6 +886,12 @@ public bool TryGetUInt64(out ulong value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetUInt64Core(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetUInt64Core(out ulong value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out ulong tmp, out int bytesConsumed)
&& span.Length == bytesConsumed)
{
@@ -732,6 +921,12 @@ public bool TryGetSingle(out float value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetSingleCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetSingleCore(out float value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out float tmp, out int bytesConsumed, _numberFormat)
&& span.Length == bytesConsumed)
{
@@ -761,6 +956,12 @@ public bool TryGetDouble(out double value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetDoubleCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetDoubleCore(out double value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out double tmp, out int bytesConsumed, _numberFormat)
&& span.Length == bytesConsumed)
{
@@ -790,6 +991,12 @@ public bool TryGetDecimal(out decimal value)
}
ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ return TryGetDecimalCore(out value, span);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryGetDecimalCore(out decimal value, ReadOnlySpan span)
+ {
if (Utf8Parser.TryParse(span, out decimal tmp, out int bytesConsumed, _numberFormat)
&& span.Length == bytesConsumed)
{
@@ -818,6 +1025,11 @@ public bool TryGetDateTime(out DateTime value)
throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
}
+ return TryGetDateTimeCore(out value);
+ }
+
+ internal bool TryGetDateTimeCore(out DateTime value)
+ {
ReadOnlySpan span = stackalloc byte[0];
if (HasValueSequence)
@@ -881,6 +1093,11 @@ public bool TryGetDateTimeOffset(out DateTimeOffset value)
throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
}
+ return TryGetDateTimeOffsetCore(out value);
+ }
+
+ internal bool TryGetDateTimeOffsetCore(out DateTimeOffset value)
+ {
ReadOnlySpan span = stackalloc byte[0];
if (HasValueSequence)
@@ -945,6 +1162,11 @@ public bool TryGetGuid(out Guid value)
throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
}
+ return TryGetGuidCore(out value);
+ }
+
+ internal bool TryGetGuidCore(out Guid value)
+ {
ReadOnlySpan span = stackalloc byte[0];
if (HasValueSequence)
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs
index cb43f7b936049..b9d2723495137 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs
@@ -2552,5 +2552,18 @@ private string DebugTokenType
JsonTokenType.True => nameof(JsonTokenType.True),
_ => ((byte)TokenType).ToString()
};
+
+ private ReadOnlySpan GetUnescapedSpan()
+ {
+ ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+ if (_stringHasEscaping)
+ {
+ int idx = span.IndexOf(JsonConstants.BackSlash);
+ Debug.Assert(idx != -1);
+ span = JsonReaderHelper.GetUnescapedSpan(span, idx);
+ }
+
+ return span;
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
index 1c4b0fae4ff12..afd541ccd7d0e 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
@@ -10,13 +10,14 @@ namespace System.Text.Json.Serialization.Converters
///
/// Default base class implementation of JsonDictionaryConverter{TCollection} .
///
- internal abstract class DictionaryDefaultConverter
+ internal abstract class DictionaryDefaultConverter
: JsonDictionaryConverter
+ where TKey : notnull
{
///
/// When overridden, adds the value to the collection.
///
- 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);
///
/// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.
@@ -31,37 +32,25 @@ protected virtual void CreateCollection(ref Utf8JsonReader reader, ref ReadStack
internal override Type ElementType => typeof(TValue);
- protected static JsonConverter GetElementConverter(ref ReadStack state)
- {
- JsonConverter converter = (JsonConverter)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? _keyConverter;
+ protected JsonConverter? _valueConverter;
- protected static JsonConverter GetValueConverter(ref WriteStack state)
+ protected static JsonConverter GetValueConverter(JsonClassInfo classInfo)
{
- JsonConverter converter = (JsonConverter)state.Current.DeclaredJsonPropertyInfo!.ConverterBase;
+ JsonConverter converter = (JsonConverter)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 GetKeyConverter(Type keyType, JsonSerializerOptions options)
+ => (JsonConverter)options.GetDictionaryKeyConverter(keyType);
+
internal sealed override bool OnTryRead(
ref Utf8JsonReader reader,
Type typeToConvert,
@@ -80,8 +69,8 @@ internal sealed override bool OnTryRead(
CreateCollection(ref reader, ref state);
- JsonConverter elementConverter = GetElementConverter(ref state);
- if (elementConverter.CanUseDirectReadOrWrite)
+ JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
+ if (valueConverter.CanUseDirectReadOrWrite)
{
// Process all elements.
while (true)
@@ -97,12 +86,12 @@ internal sealed override bool OnTryRead(
// 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
@@ -121,13 +110,13 @@ internal sealed override bool OnTryRead(
// 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);
}
}
}
@@ -184,7 +173,7 @@ internal sealed override bool OnTryRead(
}
// Process all elements.
- JsonConverter elementConverter = GetElementConverter(ref state);
+ JsonConverter elementConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
while (true)
{
if (state.Current.PropertyState == StackFramePropertyState.None)
@@ -212,7 +201,6 @@ internal sealed override bool OnTryRead(
state.Current.PropertyState = StackFramePropertyState.Name;
- // Verify property doesn't contain metadata.
if (preserveReferences)
{
ReadOnlySpan propertyName = reader.GetSpan();
@@ -222,7 +210,7 @@ internal sealed override bool OnTryRead(
}
}
- state.Current.JsonPropertyNameAsString = reader.GetString();
+ state.Current.DictionaryKey = ReadDictionaryKey(ref reader, ref state);
}
if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
@@ -246,7 +234,8 @@ internal sealed override bool OnTryRead(
return false;
}
- Add(element!, options, ref state);
+ TKey key = (TKey)state.Current.DictionaryKey!;
+ Add(key, element!, options, ref state);
state.Current.EndElement();
}
}
@@ -255,6 +244,29 @@ internal sealed override bool OnTryRead(
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 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(
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs
similarity index 66%
rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs
rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs
index 13bcea6c7d10b..b34294f9e002f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs
@@ -10,13 +10,13 @@ namespace System.Text.Json.Serialization.Converters
/// Converter for Dictionary{string, TValue} that (de)serializes as a JSON object with properties
/// representing the dictionary element key and value.
///
- internal sealed class DictionaryOfStringTValueConverter
- : DictionaryDefaultConverter
- where TCollection : Dictionary
+ internal sealed class DictionaryOfTKeyTValueConverter
+ : DictionaryDefaultConverter
+ where TCollection : Dictionary
+ 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;
}
@@ -36,7 +36,7 @@ protected internal override bool OnWriteResume(
JsonSerializerOptions options,
ref WriteStack state)
{
- Dictionary.Enumerator enumerator;
+ Dictionary.Enumerator enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
@@ -47,18 +47,20 @@ protected internal override bool OnWriteResume(
}
else
{
- enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator;
+ enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator;
}
- JsonConverter converter = GetValueConverter(ref state);
- if (!state.SupportContinuation && converter.CanUseDirectReadOrWrite)
+ JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ JsonConverter 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
@@ -74,12 +76,13 @@ protected internal override bool OnWriteResume(
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;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
index 4927187b0af77..1e586c093fa1b 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
@@ -12,15 +12,19 @@ namespace System.Text.Json.Serialization.Converters
/// representing the dictionary element key and value.
///
internal sealed class IDictionaryConverter
- : DictionaryDefaultConverter
+ : DictionaryDefaultConverter
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
- internal sealed class IDictionaryOfStringTValueConverter
- : DictionaryDefaultConverter
- where TCollection : IDictionary
+ internal sealed class IDictionaryOfTKeyTValueConverter
+ : DictionaryDefaultConverter
+ where TCollection : IDictionary
+ 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;
}
@@ -57,7 +57,7 @@ protected internal override bool OnWriteResume(
JsonSerializerOptions options,
ref WriteStack state)
{
- IEnumerator> enumerator;
+ IEnumerator> enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
@@ -68,10 +68,11 @@ protected internal override bool OnWriteResume(
}
else
{
- enumerator = (IEnumerator>)state.Current.CollectionEnumerator;
+ enumerator = (IEnumerator>)state.Current.CollectionEnumerator;
}
- JsonConverter converter = GetValueConverter(ref state);
+ JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
do
{
if (ShouldFlush(writer, ref state))
@@ -83,12 +84,12 @@ protected internal override bool OnWriteResume(
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;
@@ -106,7 +107,7 @@ internal override Type RuntimeType
{
if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface)
{
- return typeof(Dictionary);
+ return typeof(Dictionary);
}
return TypeToConvert;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs
index 56012689bc5e6..7b1cdd130f50d 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs
@@ -28,25 +28,26 @@ public override bool CanConvert(Type 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
@@ -67,61 +68,37 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer
converterType = typeof(ListOfTConverter<,>);
elementType = actualTypeToConvert.GetGenericArguments()[0];
}
- // Dictionary or deriving from Dictionary
+ // Dictionary or deriving from Dictionary
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
+ // Immutable dictionaries from System.Collections.Immutable, e.g. ImmutableDictionary
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 or deriving from IDictionary
+ // IDictionary or deriving from IDictionary
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 or deriving from IReadOnlyDictionary
+ // IReadOnlyDictionary or deriving from IReadOnlyDictionary
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
else if (typeToConvert.IsImmutableEnumerableType())
@@ -211,17 +188,21 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer
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,
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs
similarity index 65%
rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs
rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs
index 0d05ff3a1869d..b4cc76d8bd6b8 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs
@@ -6,14 +6,14 @@
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class IReadOnlyDictionaryOfStringTValueConverter
- : DictionaryDefaultConverter
- where TCollection : IReadOnlyDictionary
+ internal sealed class IReadOnlyDictionaryOfTKeyTValueConverter
+ : DictionaryDefaultConverter
+ where TCollection : IReadOnlyDictionary
+ 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)state.Current.ReturnValue!)[key] = value;
+ ((Dictionary)state.Current.ReturnValue!)[key] = value;
}
protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state)
@@ -28,7 +28,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac
protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
{
- IEnumerator> enumerator;
+ IEnumerator> enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
@@ -39,10 +39,11 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
}
else
{
- enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator;
+ enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator;
}
- JsonConverter converter = GetValueConverter(ref state);
+ JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
do
{
if (ShouldFlush(writer, ref state))
@@ -54,12 +55,13 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
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;
@@ -71,6 +73,6 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
return true;
}
- internal override Type RuntimeType => typeof(Dictionary);
+ internal override Type RuntimeType => typeof(Dictionary);
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs
similarity index 70%
rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs
rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs
index 4e6e1caa818f3..7d5b429d287d9 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs
@@ -6,14 +6,14 @@
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class ImmutableDictionaryOfStringTValueConverter
- : DictionaryDefaultConverter
- where TCollection : IReadOnlyDictionary
+ internal sealed class ImmutableDictionaryOfTKeyTValueConverter
+ : DictionaryDefaultConverter
+ where TCollection : IReadOnlyDictionary
+ 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)state.Current.ReturnValue!)[key] = value;
+ ((Dictionary)state.Current.ReturnValue!)[key] = value;
}
internal override bool CanHaveIdMetadata => false;
@@ -39,7 +39,7 @@ protected override void ConvertCollection(ref ReadStack state, JsonSerializerOpt
protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
{
- IEnumerator> enumerator;
+ IEnumerator> enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
@@ -50,10 +50,11 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
}
else
{
- enumerator = (IEnumerator>)state.Current.CollectionEnumerator;
+ enumerator = (IEnumerator>)state.Current.CollectionEnumerator;
}
- JsonConverter converter = GetValueConverter(ref state);
+ JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
+ JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo);
do
{
if (ShouldFlush(writer, ref state))
@@ -65,12 +66,13 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
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;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs
index 04da6ed163fc5..32aba9afcf3ff 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs
@@ -2,6 +2,8 @@
// 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
@@ -15,5 +17,22 @@ public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOpti
{
writer.WriteBooleanValue(value);
}
+
+ internal override bool ReadWithQuotes(ref Utf8JsonReader reader)
+ {
+ ReadOnlySpan 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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs
index 38b8211ee3619..1280d2abf0fc5 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, byte value, JsonSerializerOpti
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs
index 5b259892c4d9c..41901e39560c3 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs
@@ -25,6 +25,20 @@ public override void Write(Utf8JsonWriter writer, char value, JsonSerializerOpti
MemoryMarshal.CreateSpan(ref value, 1)
#else
value.ToString()
+#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
);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs
index e5dbbd588271f..00754b5b8ed82 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializer
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs
index 927911cb7bd54..e6eada9de0462 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSeri
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs
index 326df60822c75..0f2350fa34175 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerO
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs
index 4a2acd60865c9..5814f89c293a6 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOp
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs
index 41f7302f7db55..c4b4dd75468f8 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs
@@ -83,15 +83,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
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))
@@ -317,5 +309,91 @@ private string FormatEnumValueToString(string value, JavaScriptEncoder? encoder)
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(ref value));
+ break;
+ case TypeCode.UInt32:
+ writer.WritePropertyName(Unsafe.As(ref value));
+ break;
+ case TypeCode.UInt64:
+ writer.WritePropertyName(Unsafe.As(ref value));
+ break;
+ case TypeCode.Int64:
+ writer.WritePropertyName(Unsafe.As(ref value));
+ break;
+ case TypeCode.Int16:
+ writer.WritePropertyName(Unsafe.As(ref value));
+ break;
+ case TypeCode.UInt16:
+ writer.WritePropertyName(Unsafe.As(ref value));
+ break;
+ case TypeCode.Byte:
+ writer.WritePropertyName(Unsafe.As(ref value));
+ break;
+ case TypeCode.SByte:
+ writer.WritePropertyName(Unsafe.As(ref value));
+ break;
+ default:
+ ThrowHelper.ThrowJsonException();
+ break;
+ }
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs
index 3c7c010098ffd..6328144fc6bf7 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOpti
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs
index f68e23459af7e..8104a987625b2 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs
@@ -2,6 +2,8 @@
// 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
@@ -15,5 +17,15 @@ public override void Write(Utf8JsonWriter writer, short value, JsonSerializerOpt
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs
index abd9089c65873..f6f6712c4dff7 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptio
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs
index 0ea30c22a5644..a8817af4c924c 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOpti
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs
index acbae39e3edc3..fe680f6cde506 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs
@@ -18,5 +18,25 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp
{
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;
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs
index bbc004a29c07a..f4a2339e4831e 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, sbyte value, JsonSerializerOpt
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs
index c7c8b180ff5a8..1653a65e7e481 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOpt
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs
index bd420a35e719c..d0d958ab7a44f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs
@@ -2,9 +2,11 @@
// 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
+ internal sealed class StringConverter : JsonConverter
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
@@ -15,5 +17,25 @@ public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerO
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs
index 56f24b06a5345..44f20a21e5d9c 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, ushort value, JsonSerializerOp
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs
index 9dfc006ac6836..cea8390da69fc 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOpti
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs
index af26a458c86c9..3f07e3623a22f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs
@@ -15,5 +15,15 @@ public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOpt
{
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);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
index cc73208798ab4..25b43c861342a 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
@@ -71,6 +71,11 @@ internal bool ShouldFlush(Utf8JsonWriter writer, ref WriteStack state)
///
internal abstract bool WriteCoreAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state);
+ ///
+ /// Loosely-typed WriteWithQuotes() that forwards to strongly-typed WriteWithQuotes().
+ ///
+ 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;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs
index 5186002fe4077..0250731debbfc 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs
@@ -111,5 +111,15 @@ internal sealed override bool WriteCoreAsObject(
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();
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
index 5689440414765..bfdcaaf40b9d8 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
@@ -335,8 +335,6 @@ internal bool TryWriteDataExtensionProperty(Utf8JsonWriter writer, T value, Json
Debug.Assert(this is JsonDictionaryConverter);
- state.Current.PolymorphicJsonPropertyInfo = state.Current.DeclaredJsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PropertyInfoForClassInfo;
-
if (writer.CurrentDepth >= options.EffectiveMaxDepth)
{
ThrowHelper.ThrowJsonException_SerializerCycleDetected(options.EffectiveMaxDepth);
@@ -433,5 +431,14 @@ internal void VerifyWrite(int originalDepth, Utf8JsonWriter writer)
/// The value to convert.
/// The being used.
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);
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
index bc1f7981c30fd..fc63f11fb7ca0 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
@@ -70,7 +70,72 @@ private static Dictionary GetDefaultSimpleConverters()
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? _dictionaryKeyConverters;
+
+ private static ConcurrentDictionary GetDictionaryKeyConverters()
+ {
+ const int NumberOfConverters = 18;
+ var converters = new ConcurrentDictionary(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;
}
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
index d9a59477faa2b..74ff1bca6710d 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
@@ -15,9 +15,14 @@ internal struct ReadStackFrame
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;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
index 71d3f2ada0f49..a27c198c97e01 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
@@ -40,6 +40,13 @@ public static void ThrowNotSupportedException_ConstructorMaxOf64Parameters(Const
throw new NotSupportedException(SR.Format(SR.ConstructorMaxOf64Parameters, constructorInfo, type));
}
+ [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)
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
index 9dffc288b1a55..16b2471587091 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
@@ -612,6 +612,9 @@ public static FormatException GetFormatException(DataType dateType)
switch (dateType)
{
+ case DataType.Boolean:
+ message = SR.FormatBoolean;
+ break;
case DataType.DateTime:
message = SR.FormatDateTime;
break;
@@ -702,6 +705,7 @@ internal enum NumericType
internal enum DataType
{
+ Boolean,
DateTime,
DateTimeOffset,
Base64String,
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs
index da5219b98771d..0ed995b641d7b 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs
@@ -375,5 +375,12 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim
output[BytesPending++] = JsonConstants.Quote;
}
+
+ internal void WritePropertyName(DateTime value)
+ {
+ Span buffer = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+ JsonWriterHelper.WriteDateTimeTrimmed(buffer, value, out int bytesWritten);
+ WritePropertyNameUnescaped(buffer.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs
index 37cd479a2b735..62ba78f9e0df5 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs
@@ -374,5 +374,12 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim
output[BytesPending++] = JsonConstants.Quote;
}
+
+ internal void WritePropertyName(DateTimeOffset value)
+ {
+ Span buffer = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+ JsonWriterHelper.WriteDateTimeOffsetTrimmed(buffer, value, out int bytesWritten);
+ WritePropertyNameUnescaped(buffer.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs
index e4b05e79b30fd..626098736f94e 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs
@@ -362,5 +362,13 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(decimal value)
+ {
+ Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatDecimalLength];
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs
index f8ae05cc04c20..63047537ab372 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs
@@ -366,5 +366,14 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(double value)
+ {
+ JsonWriterHelper.ValidateDouble(value);
+ Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatDoubleLength];
+ bool result = TryFormatDouble(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs
index 60ba0b03872d0..2af8936863bc5 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs
@@ -366,5 +366,13 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float v
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(float value)
+ {
+ Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatSingleLength];
+ bool result = TryFormatSingle(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs
index b18a107c78b35..062e1ea758e6e 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs
@@ -378,5 +378,13 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid va
output[BytesPending++] = JsonConstants.Quote;
}
+
+ internal void WritePropertyName(Guid value)
+ {
+ Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatGuidLength];
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
index c5772f978005d..625d7711e0732 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
@@ -3,6 +3,7 @@
// 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;
@@ -503,5 +504,15 @@ private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOn
value.CopyTo(output.Slice(BytesPending));
BytesPending += value.Length;
}
+
+ internal void WritePropertyName(bool value)
+ {
+ Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatBooleanLength];
+
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs
index 360bea1089d0c..f24aad62ac1a7 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs
@@ -432,5 +432,18 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long va
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(int value)
+ => WritePropertyName((long)value);
+
+ internal void WritePropertyName(long value)
+ {
+ Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatInt64Length];
+
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
index ddcca39fa28f4..d0c19f67a5180 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
@@ -255,6 +255,15 @@ public void WritePropertyName(ReadOnlySpan utf8PropertyName)
_tokenType = JsonTokenType.PropertyName;
}
+ private void WritePropertyNameUnescaped(ReadOnlySpan utf8PropertyName)
+ {
+ JsonWriterHelper.ValidateProperty(utf8PropertyName);
+ WriteStringByOptionsPropertyName(utf8PropertyName);
+
+ _currentDepth &= JsonConstants.RemoveFlagsBitMask;
+ _tokenType = JsonTokenType.PropertyName;
+ }
+
private void WriteStringEscapeProperty(ReadOnlySpan utf8PropertyName, int firstEscapeIndexProp)
{
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs
index 48d9518445434..0430768b43be6 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs
@@ -441,5 +441,18 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong v
Debug.Assert(result);
BytesPending += bytesWritten;
}
+
+ internal void WritePropertyName(uint value)
+ => WritePropertyName((ulong)value);
+
+ internal void WritePropertyName(ulong value)
+ {
+ Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatUInt64Length];
+
+ bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
+ Debug.Assert(result);
+
+ WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs
new file mode 100644
index 0000000000000..98b96d5d95a0f
--- /dev/null
+++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs
@@ -0,0 +1,579 @@
+// 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
+ {
+ protected abstract TKey Key { get; }
+ protected abstract TValue Value { get; }
+ protected virtual string _expectedJson => $"{{\"{Key}\":{Value}}}";
+
+ protected virtual void Validate(Dictionary dictionary)
+ {
+ bool success = dictionary.TryGetValue(Key, out TValue value);
+ Assert.True(success);
+ Assert.Equal(Value, value);
+ }
+
+ private Dictionary BuildDictionary()
+ {
+ var dictionary = new Dictionary();
+ dictionary.Add(Key, Value);
+
+ return dictionary;
+ }
+
+ [Fact]
+ public void TestDictionaryKey()
+ {
+ Dictionary dictionary = BuildDictionary();
+
+ string json = JsonSerializer.Serialize(dictionary);
+ Assert.Equal(_expectedJson, json);
+
+ Dictionary dictionaryCopy = JsonSerializer.Deserialize>(json);
+ Validate(dictionaryCopy);
+ }
+
+ [Fact]
+ public async Task TestDictionaryKeyAsync()
+ {
+ Dictionary 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 dictionaryCopy = await JsonSerializer.DeserializeAsync>(deserializeStream);
+ Validate(dictionaryCopy);
+ }
+ }
+
+ public class DictionaryBoolKey : DictionaryKeyTestsBase
+ {
+ protected override bool Key => true;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryByteKey : DictionaryKeyTestsBase
+ {
+ protected override byte Key => byte.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryCharKey : DictionaryKeyTestsBase
+ {
+ protected override string _expectedJson => @"{""\uFFFF"":""\uFFFF""}";
+ protected override char Key => char.MaxValue;
+ protected override char Value => char.MaxValue;
+ }
+
+ public class DictionaryDateTimeKey : DictionaryKeyTestsBase
+ {
+ protected override string _expectedJson => $@"{{""{DateTime.MaxValue:O}"":1}}";
+ protected override DateTime Key => DateTime.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryDateTimeOffsetKey : DictionaryKeyTestsBase
+ {
+ protected override string _expectedJson => $@"{{""{DateTimeOffset.MaxValue:O}"":1}}";
+ protected override DateTimeOffset Key => DateTimeOffset.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryDecimalKey : DictionaryKeyTestsBase
+ {
+ protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(decimal.MaxValue)}"":1}}";
+ protected override decimal Key => decimal.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryDoubleKey : DictionaryKeyTestsBase
+ {
+ protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(double.MaxValue)}"":1}}";
+ protected override double Key => double.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryEnumKey : DictionaryKeyTestsBase
+ {
+ protected override MyEnum Key => MyEnum.Foo;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryEnumFlagsKey : DictionaryKeyTestsBase
+ {
+ protected override MyEnumFlags Key => MyEnumFlags.Foo | MyEnumFlags.Bar;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryGuidKey : DictionaryKeyTestsBase
+ {
+ // 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
+ {
+ protected override short Key => short.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryInt32Key : DictionaryKeyTestsBase
+ {
+ protected override int Key => int.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryInt64Key : DictionaryKeyTestsBase
+ {
+ protected override long Key => long.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionarySByteKey : DictionaryKeyTestsBase
+ {
+ protected override sbyte Key => sbyte.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionarySingleKey : DictionaryKeyTestsBase
+ {
+ protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(float.MaxValue)}"":1}}";
+ protected override float Key => float.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryStringKey : DictionaryKeyTestsBase
+ {
+ protected override string Key => "KeyString";
+ protected override int Value => 1;
+ }
+
+ public class DictionaryUInt16Key : DictionaryKeyTestsBase
+ {
+ protected override ushort Key => ushort.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryUInt32Key : DictionaryKeyTestsBase
+ {
+ protected override uint Key => uint.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public class DictionaryUInt64Key : DictionaryKeyTestsBase
+ {
+ protected override ulong Key => ulong.MaxValue;
+ protected override int Value => 1;
+ }
+
+ public abstract class DictionaryUnsupportedKeyTestsBase
+ {
+ private Dictionary _dictionary => BuildDictionary();
+ protected abstract TKey Key { get; }
+ private Dictionary BuildDictionary()
+ {
+ return new Dictionary() { { Key, default } };
+ }
+
+ [Fact]
+ public void ThrowUnsupported_Serialize()
+ => Assert.Throws(() => JsonSerializer.Serialize(_dictionary));
+
+ [Fact]
+ public Task ThrowUnsupported_SerializeAsync()
+ => Assert.ThrowsAsync(() => JsonSerializer.SerializeAsync(new MemoryStream(), _dictionary));
+
+ [Fact]
+ public void ThrowUnsupported_Deserialize() => Assert.Throws(()
+ => JsonSerializer.Deserialize>(@"{""foo"":1}"));
+
+ [Fact]
+ public Task ThrowUnsupported_DeserializeAsync() => Assert.ThrowsAsync(()
+ => JsonSerializer.DeserializeAsync>(new MemoryStream(Encoding.UTF8.GetBytes(@"{""foo"":1}"))).AsTask());
+
+ [Fact]
+ public void DoesNotThrowIfEmpty_Serialize()
+ => JsonSerializer.Serialize(new Dictionary());
+
+ [Fact]
+ public Task DoesNotThrowIfEmpty_SerializeAsync()
+ => JsonSerializer.SerializeAsync(new MemoryStream(), new Dictionary());
+
+ [Fact]
+ public void DoesNotThrowIfEmpty_Deserialize()
+ => JsonSerializer.Deserialize>("{}");
+
+ [Fact]
+ public Task DoesNotThrowIfEmpty_DeserializeAsync()
+ => JsonSerializer.DeserializeAsync>(new MemoryStream(Encoding.UTF8.GetBytes("{}"))).AsTask();
+ }
+
+ public class DictionaryMyPublicClassKeyUnsupported : DictionaryUnsupportedKeyTestsBase
+ {
+ protected override MyPublicClass Key => new MyPublicClass();
+ }
+
+ public class DictionaryMyPublicStructKeyUnsupported : DictionaryUnsupportedKeyTestsBase
+ {
+ protected override MyPublicStruct Key => new MyPublicStruct();
+ }
+
+ public class DictionaryUriKeyUnsupported : DictionaryUnsupportedKeyTestsBase
+ {
+ protected override Uri Key => new Uri("http://foo");
+ }
+
+ public class DictionaryObjectKeyUnsupported : DictionaryUnsupportedKeyTestsBase
+ {
+ protected override object Key => new object();
+ }
+
+ public class DictionaryPolymorphicKeyUnsupported : DictionaryUnsupportedKeyTestsBase
+ {
+ protected override object Key => new Uri("http://foo");
+ }
+
+ public class DictionaryNonStringKeyTests
+ {
+ [Fact]
+ public void TestGenericDictionaryKeyObject()
+ {
+ var dictionary = new Dictionary();
+ // 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(() => JsonSerializer.Deserialize>(json));
+
+ var @object = new ClassWithDictionary { Dictionary = dictionary };
+ json = JsonSerializer.Serialize(@object);
+ Assert.Equal($@"{{""Dictionary"":{expected}}}", json);
+ Assert.Throws(() => JsonSerializer.Deserialize(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(json);
+ Assert.IsType>(dictionary);
+
+ dictionary = JsonSerializer.Deserialize(json);
+ foreach (object key in dictionary.Keys)
+ {
+ Assert.IsType(key);
+ }
+
+ var @object = new ClassWithIDictionary { Dictionary = dictionary };
+ json = JsonSerializer.Serialize(@object);
+ Assert.Equal($@"{{""Dictionary"":{expected}}}", json);
+
+ @object = JsonSerializer.Deserialize(json);
+ Assert.IsType>(@object.Dictionary);
+ }
+
+ [Theory] // Extend this test when support for more types is added.
+ [InlineData(@"{""1.1"":1}", typeof(Dictionary))]
+ [InlineData(@"{""{00000000-0000-0000-0000-000000000000}"":1}", typeof(Dictionary))]
+ public void ThrowOnInvalidFormat(string json, Type typeToConvert)
+ {
+ JsonException ex = Assert.Throws(() => 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))]
+ [InlineData(@"{""{00000000-0000-0000-0000-000000000000}"":1}", typeof(Dictionary))]
+ public async Task ThrowOnInvalidFormatAsync(string json, Type typeToConvert)
+ {
+ byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
+ Stream stream = new MemoryStream(jsonBytes);
+
+ JsonException ex = await Assert.ThrowsAsync(async () => await JsonSerializer.DeserializeAsync(stream, typeToConvert));
+ Assert.Contains(typeToConvert.ToString(), ex.Message);
+ }
+
+ [Fact]
+ public static void TestNotSuportedExceptionIsThrown()
+ {
+ // Dictionary>
+ Assert.Null(JsonSerializer.Deserialize>("null"));
+ Assert.Throws(() => JsonSerializer.Deserialize>("\"\""));
+ Assert.NotNull(JsonSerializer.Deserialize>("{}"));
+
+ Assert.Throws(() => JsonSerializer.Deserialize>(@"{""Foo"":1}"));
+
+ // UnsupportedDictionaryWrapper
+ Assert.Throws(() => JsonSerializer.Deserialize("\"\""));
+ Assert.NotNull(JsonSerializer.Deserialize("{}"));
+ Assert.Null(JsonSerializer.Deserialize("null"));
+ Assert.NotNull(JsonSerializer.Deserialize(@"{""Dictionary"":null}"));
+ Assert.NotNull(JsonSerializer.Deserialize(@"{""Dictionary"":{}}"));
+
+ Assert.Throws(() => JsonSerializer.Deserialize(@"{""Dictionary"":{""Foo"":1}}"));
+ }
+
+ [Fact]
+ public void TestPolicyOnlyAppliesToString()
+ {
+ var opts = new JsonSerializerOptions
+ {
+ DictionaryKeyPolicy = new FixedNamingPolicy()
+ };
+
+ var stringIntDictionary = new Dictionary { { "1", 1 } };
+ string json = JsonSerializer.Serialize(stringIntDictionary, opts);
+ Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json);
+
+ var intIntDictionary = new Dictionary { { 1, 1 } };
+ json = JsonSerializer.Serialize(intIntDictionary, opts);
+ Assert.Equal(@"{""1"":1}", json);
+
+ var objectIntDictionary = new Dictionary { { "1", 1 } };
+ json = JsonSerializer.Serialize(objectIntDictionary, opts);
+ Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json);
+
+ objectIntDictionary = new Dictionary { { 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 { { "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 { { 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 { { "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 { { 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();
+ myEnumIntDictionary.Add((MyEnum)(-1), 1);
+
+ string json = JsonSerializer.Serialize(myEnumIntDictionary);
+ Assert.Equal(@"{""-1"":1}", json);
+
+ myEnumIntDictionary = JsonSerializer.Deserialize>(json);
+ Assert.Equal(1, myEnumIntDictionary[(MyEnum)(-1)]);
+
+ var myEnumFlagsIntDictionary = new Dictionary();
+ myEnumFlagsIntDictionary.Add((MyEnumFlags)(-1), 1);
+
+ json = JsonSerializer.Serialize(myEnumFlagsIntDictionary);
+ Assert.Equal(@"{""-1"":1}", json);
+
+ myEnumFlagsIntDictionary = JsonSerializer.Deserialize>(json);
+ Assert.Equal(1, myEnumFlagsIntDictionary[(MyEnumFlags)(-1)]);
+ }
+
+ [Theory]
+ [MemberData(nameof(DictionaryKeysWithSpecialCharacters))]
+ public void EnsureNonStringKeysDontGetEscapedOnSerialize(object key, string expectedKeySerialized)
+ {
+ Dictionary root = new Dictionary();
+ root.Add(key, 1);
+
+ string json = JsonSerializer.Serialize(root);
+ Assert.Contains(expectedKeySerialized, json);
+ }
+
+ public static IEnumerable DictionaryKeysWithSpecialCharacters =>
+ new List
+ {
+ 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 EscapedMemberData =>
+ new List
+ {
+ new object[] { @"\u0031\u0032\u0037",
+ sbyte.MaxValue, typeof(Dictionary) },
+ new object[] { @"\u0032\u0035\u0035",
+ byte.MaxValue, typeof(Dictionary) },
+ new object[] { @"\u0033\u0032\u0037\u0036\u0037",
+ short.MaxValue, typeof(Dictionary) },
+ new object[] { @"\u0036\u0035\u0035\u0033\u0035",
+ ushort.MaxValue, typeof(Dictionary) },
+ new object[] { @"\u0032\u0031\u0034\u0037\u0034\u0038\u0033\u0036\u0034\u0037",
+ int.MaxValue, typeof(Dictionary) },
+ new object[] { @"\u0034\u0032\u0039\u0034\u0039\u0036\u0037\u0032\u0039\u0035",
+ uint.MaxValue, typeof(Dictionary) },
+ 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) },
+ 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) },
+ // 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) },
+ new object[] { @"\u0033\u002e\u0031\u0032\u0035\u0065\u0037",
+ 3.125e7d, typeof(Dictionary) },
+ new object[] { @"\u0033\u002e\u0031\u0032\u0035\u0065\u0037",
+ 3.125e7m, typeof(Dictionary) },
+ 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) },
+ 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) },
+ 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) },
+ new object[] { @"\u0042\u0061\u0072",
+ MyEnum.Bar, typeof(Dictionary) },
+ new object[] { @"\u0042\u0061\u0072\u002c\u0042\u0061\u007a",
+ MyEnumFlags.Bar | MyEnumFlags.Baz, typeof(Dictionary) },
+ new object[] { @"\u002b", '+', typeof(Dictionary) }
+ };
+ }
+
+ 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 Dictionary { get; set; }
+ }
+
+ private class ClassWithExtensionData
+ {
+ [JsonExtensionData]
+ public Dictionary Overflow { get; set; }
+ }
+
+ private class UnsupportedDictionaryWrapper
+ {
+ public Dictionary 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;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs
index bf1fb5a1587aa..9dd3c4d4a2fcb 100644
--- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs
+++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs
@@ -641,13 +641,6 @@ public class Poco
public int Id { get; set; }
}
- [Fact]
- public static void FirstGenericArgNotStringFail()
- {
- Assert.Throws(() => JsonSerializer.Deserialize>(@"{1:1}"));
- Assert.Throws(() => JsonSerializer.Deserialize>(@"{1:1}"));
- }
-
[Fact]
public static void DictionaryOfList()
{
@@ -1619,8 +1612,7 @@ public static void DictionaryNotSupported()
NotSupportedException ex = Assert.Throws(() => JsonSerializer.Deserialize(json));
// The exception contains the type.
- Assert.Contains(typeof(Dictionary).ToString(), ex.Message);
- Assert.DoesNotContain("Path: ", ex.Message);
+ Assert.Contains(typeof(Dictionary).ToString(), ex.Message);
}
[Fact]
@@ -1840,12 +1832,12 @@ public static void NullDictionaryValuesShouldDeserializeAsNull()
public class ClassWithNotSupportedDictionary
{
- public Dictionary MyDictionary { get; set; }
+ public Dictionary MyDictionary { get; set; }
}
public class ClassWithNotSupportedDictionaryButIgnored
{
- [JsonIgnore] public Dictionary MyDictionary { get; set; }
+ [JsonIgnore] public Dictionary MyDictionary { get; set; }
}
public class AllSingleUpperPropertiesParent
@@ -2143,15 +2135,6 @@ public static void VerifyDictionaryThatHasIncomatibleEnumeratorWithPoco()
Assert.Throws(() => JsonSerializer.Serialize(dictionary));
}
-
- [Fact]
- public static void VerifyIDictionaryWithNonStringKey()
- {
- IDictionary dictionary = new Hashtable();
- dictionary.Add(1, "value");
- Assert.Throws(() => JsonSerializer.Serialize(dictionary));
- }
-
private class ClassWithoutParameterlessCtor
{
public ClassWithoutParameterlessCtor(int num) { }
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs
index 3e9eae828e837..70340547c6b63 100644
--- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs
+++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs
@@ -1148,10 +1148,10 @@ private static IEnumerable CustomInterfaces_Dictionaries()
}
[Fact]
- public static void IReadOnlyDictionary_NonStringKey_NotSupported()
+ public static void IReadOnlyDictionary_NotSupportedKey()
{
- Assert.Throws(() => JsonSerializer.Deserialize>(""));
- Assert.Throws(() => JsonSerializer.Serialize(new GenericIReadOnlyDictionaryWrapper()));
+ Assert.Throws(() => JsonSerializer.Deserialize>(@"{""http://foo"":1}"));
+ Assert.Throws(() => JsonSerializer.Serialize(new GenericIReadOnlyDictionaryWrapper(new Dictionary { { new Uri("http://foo"), 1 } })));
}
}
}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs b/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs
index 37b2765cb2b2e..b97696d5facf4 100644
--- a/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs
+++ b/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs
@@ -76,7 +76,7 @@ public static void CustomUnsupportedDictionaryConverter()
{
DictionaryWrapper = new UnsupportedDictionaryWrapper()
};
- wrapper.DictionaryWrapper[1] = 1;
+ wrapper.DictionaryWrapper[new int[,] { }] = 1;
// Without converter, we throw.
Assert.Throws(() => JsonSerializer.Deserialize(json));
@@ -128,7 +128,7 @@ public class ListWrapper : List { }
public class DictionaryWrapper : Dictionary { }
- public class UnsupportedDictionaryWrapper : Dictionary { }
+ public class UnsupportedDictionaryWrapper : Dictionary { }
public class DerivedTypesWrapper
{
diff --git a/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs b/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
index 8c95c471e509e..73906d8b745c3 100644
--- a/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
+++ b/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
@@ -866,9 +866,8 @@ public static void ExtensionProperty_InvalidDictionary()
ClassWithInvalidExtensionPropertyStringString obj1 = new ClassWithInvalidExtensionPropertyStringString();
Assert.Throws(() => JsonSerializer.Serialize(obj1));
- // This fails with NotSupportedException since all Dictionaries currently need to have a string TKey.
ClassWithInvalidExtensionPropertyObjectString obj2 = new ClassWithInvalidExtensionPropertyObjectString();
- Assert.Throws(() => JsonSerializer.Serialize(obj2));
+ Assert.Throws(() => JsonSerializer.Serialize(obj2));
}
private class ClassWithExtensionPropertyAlreadyInstantiated
diff --git a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs
index c2fd2148b3284..8fb2731ae7876 100644
--- a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs
+++ b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs
@@ -851,18 +851,41 @@ public static void JsonIgnoreAttribute_UnsupportedCollection()
JsonSerializerOptions options = new JsonSerializerOptions();
// Unsupported collections will throw on serialize by default.
- Assert.Throws(() => JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options));
+ // Only when the collection contains elements.
- // Unsupported collections will throw on deserialize by default.
+ var dictionary = new Dictionary();
+ // Uri is an unsupported dictionary key.
+ dictionary.Add(new Uri("http://foo"), "bar");
+
+ var concurrentDictionary = new ConcurrentDictionary(dictionary);
+
+ var instance = new ClassWithUnsupportedDictionary()
+ {
+ MyConcurrentDict = concurrentDictionary,
+ MyIDict = dictionary
+ };
+
+ var instanceWithIgnore = new ClassWithIgnoredUnsupportedDictionary
+ {
+ MyConcurrentDict = concurrentDictionary,
+ MyIDict = dictionary
+ };
+
+ Assert.Throws(() => JsonSerializer.Serialize(instance, options));
+
+ // Unsupported collections will throw on deserialize by default if they contain elements.
options = new JsonSerializerOptions();
Assert.Throws(() => JsonSerializer.Deserialize(wrapperJson, options));
options = new JsonSerializerOptions();
- // Unsupported collections will throw on serialize by default.
- Assert.Throws(() => JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options));
+ // Unsupported collections will throw on serialize by default if they contain elements.
+ Assert.Throws(() => 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(json, options);
Assert.Null(obj.MyDict);
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj
index ddfccacb6f8a5..918787c3201ec 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj
@@ -1,4 +1,4 @@
-
+
$(NetCoreAppCurrent);$(NetFrameworkCurrent)
true
@@ -41,6 +41,7 @@
+