<data name="SerializationDuplicateTypeAttribute" xml:space="preserve">
<value>The type '{0}' cannot have more than one property that has the attribute '{1}'.</value>
</data>
- <data name="SerializationNotSupportedCollectionType" xml:space="preserve">
- <value>The collection type '{0}' is not supported.</value>
+ <data name="SerializationNotSupportedType" xml:space="preserve">
+ <value>The type '{0}' is not supported.</value>
</data>
- <data name="SerializationNotSupportedCollection" xml:space="preserve">
- <value>The collection type '{0}' on '{1}' is not supported.</value>
+ <data name="SerializationNotSupported" xml:space="preserve">
+ <value>The type '{0}' on '{1}' is not supported.</value>
</data>
<data name="InvalidCharacterAtStartOfComment" xml:space="preserve">
<value>'{0}' is invalid after '/' at the beginning of the comment. Expected either '/' or '*'.</value>
<data name="MetadataInvalidPropertyWithLeadingDollarSign" xml:space="preserve">
<value>Properties that start with '$' are not allowed on preserve mode, either escape the character or turn off preserve references by setting ReferenceHandling to ReferenceHandling.Default.</value>
</data>
-</root>
+</root>
\ No newline at end of file
<Compile Include="System\Text\Json\Serialization\Converters\JsonKeyValuePairConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonListOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonObjectDefaultConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\JsonObjectFactoryConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\Converters\JsonObjectConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonQueueOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonStackOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterBoolean.cs" />
<Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonCollectionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverter.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverter.ReadAhead.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverterAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonDefaultNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonDictionaryConverter.cs" />
<Compile Include="System\Text\Json\Serialization\JsonExtensionDataAttribute.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonIEnumerableConverter.cs" />
<Compile Include="System\Text\Json\Serialization\JsonIgnoreAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonObjectConverter.cs" />
<Compile Include="System\Text\Json\Node\JsonObjectProperty.cs" />
<Compile Include="System\Text\Json\Node\JsonString.cs" />
</ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
// 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;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace System.Text.Json
{
internal static partial class JsonHelpers
{
+ /// <summary>
+ /// Returns the span for the given reader.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<byte> GetSpan(this ref Utf8JsonReader reader)
+ {
+ return reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+ }
+
#if !BUILDING_INBOX_LIBRARY
/// <summary>
/// Returns <see langword="true"/> if <paramref name="value"/> is a valid Unicode scalar
public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0';
/// <summary>
+ /// Perform a Read() with a Debug.Assert verifying the reader did not return false.
+ /// This should be called when the Read() return value is not used, such as non-Stream cases where there is only one buffer.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadWithVerify(this ref Utf8JsonReader reader)
+ {
+ bool result = reader.Read();
+ Debug.Assert(result);
+ }
+
+ /// <summary>
/// Calls Encoding.UTF8.GetString that supports netstandard.
/// </summary>
/// <param name="bytes">The utf8 bytes to convert.</param>
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
: JsonIEnumerableDefaultConverter<TCollection, TElement>
where TCollection: IEnumerable
{
- internal override bool CanHaveMetadata => false;
+ internal override bool CanHaveIdMetadata => false;
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is List<TElement>);
((List<TElement>)state.Current.ReturnValue!).Add(value);
}
protected override void ConvertCollection(ref ReadStack state, JsonSerializerOptions options)
{
+ Debug.Assert(state.Current.ReturnValue is List<TElement>);
List<TElement> list = (List<TElement>)state.Current.ReturnValue!;
state.Current.ReturnValue = list.ToArray();
}
protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
{
+ Debug.Assert(value is TElement[]);
TElement[] array = (TElement[])(IEnumerable)value;
int index = state.Current.EnumeratorIndex;
state.Current.EnumeratorIndex = ++index;
return false;
}
-
- state.Current.EndElement();
}
}
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is TCollection);
((TCollection)state.Current.ReturnValue!).Enqueue(value);
}
{
if (state.Current.JsonClassInfo.CreateObject == null)
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
}
state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
}
-
- internal override Type RuntimeType => typeof(Queue<TElement>);
}
}
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is TCollection);
((TCollection)state.Current.ReturnValue!).Push(value);
}
{
if (state.Current.JsonClassInfo.CreateObject == null)
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
}
state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
}
-
- internal override Type RuntimeType => TypeToConvert;
}
}
// 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;
+
namespace System.Text.Json.Serialization.Converters
{
/// <summary>
protected abstract void Add(TValue value, JsonSerializerOptions options, ref ReadStack state);
/// <summary>
- /// When overridden, converts the temporary collection held in state.ReturnValue to the final collection.
+ /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.
/// This is used with immutable collections.
/// </summary>
protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }
protected static JsonConverter<TValue> GetElementConverter(ref ReadStack state)
{
- JsonConverter<TValue>? converter = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase as JsonConverter<TValue>;
- if (converter == null)
- {
- state.Current.JsonClassInfo.ElementClassInfo.PolicyProperty.ThrowCollectionNotSupportedException();
- }
+ JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
+ Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
- return converter!;
+ return converter;
}
protected string GetKeyName(string key, ref WriteStack state, JsonSerializerOptions options)
return key;
}
- protected JsonConverter<TValue> GetValueConverter(ref WriteStack state)
+ protected static JsonConverter<TValue> GetValueConverter(ref WriteStack state)
{
- JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.DeclaredJsonPropertyInfo.ConverterBase;
- if (converter == null)
- {
- state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ThrowCollectionNotSupportedException();
- }
+ JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.DeclaredJsonPropertyInfo!.ConverterBase;
+ Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
- return converter!;
+ return converter;
}
- internal override sealed bool OnTryRead(
+ internal sealed override bool OnTryRead(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options,
JsonConverter<TValue> elementConverter = GetElementConverter(ref state);
if (elementConverter.CanUseDirectReadOrWrite)
{
+ // Process all elements.
while (true)
{
// Read the key name.
- reader.Read();
+ reader.ReadWithVerify();
if (reader.TokenType == JsonTokenType.EndObject)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}
- state.Current.KeyName = reader.GetString();
+ state.Current.JsonPropertyNameAsString = reader.GetString();
// Read the value and add.
- reader.Read();
+ reader.ReadWithVerify();
TValue element = elementConverter.Read(ref reader, typeof(TValue), options);
Add(element, options, ref state);
}
}
else
{
+ // Process all elements.
while (true)
{
// Read the key name.
- reader.Read();
+ reader.ReadWithVerify();
if (reader.TokenType == JsonTokenType.EndObject)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}
- state.Current.KeyName = reader.GetString();
+ state.Current.JsonPropertyNameAsString = reader.GetString();
- // Read the value and add.
- reader.Read();
+ 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);
}
}
else
{
- if (state.Current.ObjectState < StackFrameObjectState.StartToken)
+ // Slower path that supports continuation and preserved references.
+
+ if (state.Current.ObjectState == StackFrameObjectState.None)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
}
// Handle the metadata properties.
- if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetataPropertyValue)
+ if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetadataPropertyValue)
{
- if (this.ResolveMetadata(ref reader, ref state, out value))
+ if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
{
if (state.Current.ObjectState == StackFrameObjectState.MetadataRefPropertyEndObject)
{
+ value = (TCollection)state.Current.ReturnValue!;
return true;
}
}
else
{
+ value = default!;
return false;
}
}
+ // Create the dictionary.
if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
{
CreateCollection(ref state);
if (state.Current.MetadataId != null)
{
- if (!CanHaveMetadata)
- {
- ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(TypeToConvert);
- }
+ Debug.Assert(CanHaveIdMetadata);
value = (TCollection)state.Current.ReturnValue!;
if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, value))
{
- // Reset so JsonPath throws exception with $id in it.
- state.Current.MetadataPropertyName = MetadataPropertyName.Id;
-
- ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId);
+ ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
}
}
state.Current.ObjectState = StackFrameObjectState.CreatedObject;
}
+ // Process all elements.
JsonConverter<TValue> elementConverter = GetElementConverter(ref state);
while (true)
{
- if (state.Current.PropertyState < StackFramePropertyState.ReadName)
+ if (state.Current.PropertyState == StackFramePropertyState.None)
{
state.Current.PropertyState = StackFramePropertyState.ReadName;
// Verify property doesn't contain metadata.
if (shouldReadPreservedReferences)
{
- ReadOnlySpan<byte> propertyName = JsonSerializer.GetSpan(ref reader);
+ ReadOnlySpan<byte> propertyName = reader.GetSpan();
if (propertyName.Length > 0 && propertyName[0] == '$')
{
- ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader);
+ ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
}
}
- state.Current.KeyName = reader.GetString();
+ state.Current.JsonPropertyNameAsString = reader.GetString();
}
if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
if (state.Current.PropertyState < StackFramePropertyState.TryRead)
{
- // Read the value and add.
+ // Get the value from the converter and add it.
bool success = elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element);
if (!success)
{
return true;
}
- internal override sealed bool OnTryWrite(
+ internal sealed override bool OnTryWrite(
Utf8JsonWriter writer,
TCollection dictionary,
JsonSerializerOptions options,
ref WriteStack state)
{
- bool success;
-
if (dictionary == null)
{
writer.WriteNullValue();
- success = true;
+ return true;
}
- else
+
+ if (!state.Current.ProcessedStartToken)
{
- if (!state.Current.ProcessedStartToken)
- {
- state.Current.ProcessedStartToken = true;
- writer.WriteStartObject();
+ state.Current.ProcessedStartToken = true;
+ writer.WriteStartObject();
- if (options.ReferenceHandling.ShouldWritePreservedReferences())
+ if (options.ReferenceHandling.ShouldWritePreservedReferences())
+ {
+ if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
{
- if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
- {
- writer.WriteEndObject();
- return true;
- }
+ return true;
}
-
- state.Current.DeclaredJsonPropertyInfo = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!;
}
- success = OnWriteResume(writer, dictionary, options, ref state);
- if (success)
+ state.Current.DeclaredJsonPropertyInfo = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!;
+ }
+
+ bool success = OnWriteResume(writer, dictionary, options, ref state);
+ if (success)
+ {
+ if (!state.Current.ProcessedEndToken)
{
- if (!state.Current.ProcessedEndToken)
- {
- state.Current.ProcessedEndToken = true;
- writer.WriteEndObject();
- }
+ state.Current.ProcessedEndToken = true;
+ writer.WriteEndObject();
}
}
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
{
- string key = state.Current.KeyName!;
+ Debug.Assert(state.Current.ReturnValue is TCollection);
+
+ string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
}
{
if (state.Current.JsonClassInfo.CreateObject == null)
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
}
state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is Dictionary<string, TValue>.Enumerator);
enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
}
{
do
{
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
TValue element = enumerator.Current.Value;
if (state.Current.PropertyState < StackFramePropertyState.Name)
{
return false;
}
- state.Current.EndElement();
+ state.Current.EndDictionaryElement();
} while (enumerator.MoveNext());
}
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is ICollection<TElement>);
((ICollection<TElement>)state.Current.ReturnValue!).Add(value);
}
{
JsonClassInfo classInfo = state.Current.JsonClassInfo;
- if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+ if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- else
- {
- TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
- if (returnValue.IsReadOnly)
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
- }
+ TCollection returnValue = (TCollection)classInfo.CreateObject()!;
- state.Current.ReturnValue = returnValue;
+ if (returnValue.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
}
+
+ state.Current.ReturnValue = returnValue;
}
}
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
/// representing the dictionary element key and value.
/// </summary>
internal sealed class JsonIDictionaryConverter<TCollection>
- : JsonDictionaryDefaultConverter<TCollection, object>
+ : JsonDictionaryDefaultConverter<TCollection, object?>
where TCollection : IDictionary
{
- protected override void Add(object value, JsonSerializerOptions options, ref ReadStack state)
+ protected override void Add(object? value, JsonSerializerOptions options, ref ReadStack state)
{
- string key = state.Current.KeyName!;
+ Debug.Assert(state.Current.ReturnValue is IDictionary);
+
+ string key = state.Current.JsonPropertyNameAsString!;
((IDictionary)state.Current.ReturnValue!)[key] = value;
}
{
JsonClassInfo classInfo = state.Current.JsonClassInfo;
- if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+ if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- else
- {
- TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
- if (returnValue.IsReadOnly)
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
- }
+ TCollection returnValue = (TCollection)classInfo.CreateObject()!;
- state.Current.ReturnValue = returnValue;
+ if (returnValue.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
}
+
+ state.Current.ReturnValue = returnValue;
}
}
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IDictionaryEnumerator);
enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator;
}
- JsonConverter<object> converter = GetValueConverter(ref state);
+ JsonConverter<object?> converter = GetValueConverter(ref state);
do
{
- if (!(enumerator.Key is string key))
+ if (enumerator.Key is string key)
{
- // todo: add test for this.
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.DeclaredJsonPropertyInfo.RuntimePropertyType!);
- Diagnostics.Debug.Assert(false);
- return false;
- }
+ key = GetKeyName(key, ref state, options);
+ writer.WritePropertyName(key);
- key = GetKeyName(key, ref state, options);
- writer.WritePropertyName(key);
+ object? element = enumerator.Value;
+ if (!converter.TryWrite(writer, element, options, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
- object? element = enumerator.Value;
- if (!converter.TryWrite(writer, element!, options, ref state))
+ state.Current.EndDictionaryElement();
+ }
+ else
{
- state.Current.CollectionEnumerator = enumerator;
- return false;
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.DeclaredJsonPropertyInfo!.RuntimePropertyType!);
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
{
- string key = state.Current.KeyName!;
+ Debug.Assert(state.Current.ReturnValue is TCollection);
+
+ string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
}
{
JsonClassInfo classInfo = state.Current.JsonClassInfo;
- if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+ if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- else
- {
- TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
- if (returnValue.IsReadOnly)
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
- }
+ TCollection returnValue = (TCollection)classInfo.CreateObject()!;
- state.Current.ReturnValue = returnValue;
+ if (returnValue.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
}
+
+ state.Current.ReturnValue = returnValue;
}
}
}
else
{
- enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<KeyValuePair<string, TValue>>);
+ enumerator = (IEnumerator<KeyValuePair<string, TValue>>)state.Current.CollectionEnumerator;
}
JsonConverter<TValue> converter = GetValueConverter(ref state);
do
{
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
string key = GetKeyName(enumerator.Current.Key, ref state, options);
writer.WritePropertyName(key);
return false;
}
- state.Current.EndElement();
+ state.Current.EndDictionaryElement();
} while (enumerator.MoveNext());
return true;
/// </summary>
/// <typeparam name="TCollection"></typeparam>
internal sealed class JsonIEnumerableConverter<TCollection>
- : JsonIEnumerableDefaultConverter<TCollection, object>
+ : JsonIEnumerableDefaultConverter<TCollection, object?>
where TCollection : IEnumerable
{
- protected override void Add(object value, ref ReadStack state)
+ protected override void Add(object? value, ref ReadStack state)
{
- ((List<object>)state.Current.ReturnValue!).Add(value);
+ Debug.Assert(state.Current.ReturnValue is List<object?>);
+ ((List<object?>)state.Current.ReturnValue!).Add(value);
}
protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- state.Current.ReturnValue = new List<object>();
+ state.Current.ReturnValue = new List<object?>();
}
// Consider overriding ConvertCollection to convert the list to an array since a List is mutable.
enumerator = state.Current.CollectionEnumerator;
}
- JsonConverter<object> converter = GetElementConverter(ref state);
+ JsonConverter<object?> converter = GetElementConverter(ref state);
do
{
if (ShouldFlush(writer, ref state))
return false;
}
- if (!converter.TryWrite(writer, enumerator.Current!, options, ref state))
+ if (!converter.TryWrite(writer, enumerator.Current, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
}
- internal override Type RuntimeType => typeof(List<object>);
+ internal override Type RuntimeType => typeof(List<object?>);
}
}
/// </summary>
internal class JsonIEnumerableConverterFactory : JsonConverterFactory
{
- private static readonly JsonIDictionaryConverter<IDictionary> s_IDictionaryConverter = new JsonIDictionaryConverter<IDictionary>();
- private static readonly JsonIEnumerableConverter<IEnumerable> s_IEnumerableConverter = new JsonIEnumerableConverter<IEnumerable>();
- private static readonly JsonIListConverter<IList> s_IListConverter = new JsonIListConverter<IList>();
+ private static readonly JsonIDictionaryConverter<IDictionary> s_converterForIDictionary = new JsonIDictionaryConverter<IDictionary>();
+ private static readonly JsonIEnumerableConverter<IEnumerable> s_converterForIEnumerable = new JsonIEnumerableConverter<IEnumerable>();
+ private static readonly JsonIListConverter<IList> s_converterForIList = new JsonIListConverter<IList>();
public override bool CanConvert(Type typeToConvert)
{
{
JsonConverter? converter = null;
Type converterType;
+ Type[] genericArgs;
Type? elementType = null;
Type? actualTypeToConvert;
// Dictionary<string,> or deriving from Dictionary<string,>
else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericBaseClass(typeof(Dictionary<,>))) != null)
{
- if (actualTypeToConvert.GetGenericArguments()[0] == typeof(string))
+ genericArgs = actualTypeToConvert.GetGenericArguments();
+ if (genericArgs[0] == typeof(string))
{
converterType = typeof(JsonDictionaryOfStringTValueConverter<,>);
- elementType = actualTypeToConvert.GetGenericArguments()[1];
+ elementType = genericArgs[1];
}
else
{
// Immutable dictionaries from System.Collections.Immutable, e.g. ImmutableDictionary<string, TValue>
else if (typeToConvert.IsImmutableDictionaryType())
{
- if (typeToConvert.GetGenericArguments()[0] == typeof(string))
+ genericArgs = typeToConvert.GetGenericArguments();
+ if (genericArgs[0] == typeof(string))
{
converterType = typeof(JsonImmutableDictionaryOfStringTValueConverter<,>);
- elementType = typeToConvert.GetGenericArguments()[1];
+ elementType = genericArgs[1];
}
else
{
// IDictionary<string,> or deriving from IDictionary<string,>
else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IDictionary<,>))) != null)
{
- if (actualTypeToConvert.GetGenericArguments()[0] == typeof(string))
+ genericArgs = actualTypeToConvert.GetGenericArguments();
+ if (genericArgs[0] == typeof(string))
{
converterType = typeof(JsonIDictionaryOfStringTValueConverter<,>);
- elementType = actualTypeToConvert.GetGenericArguments()[1];
+ elementType = genericArgs[1];
}
else
{
// IReadOnlyDictionary<string,> or deriving from IReadOnlyDictionary<string,>
else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IReadOnlyDictionary<,>))) != null)
{
- if (actualTypeToConvert.GetGenericArguments()[0] == typeof(string))
+ genericArgs = actualTypeToConvert.GetGenericArguments();
+ if (genericArgs[0] == typeof(string))
{
converterType = typeof(JsonIReadOnlyDictionaryOfStringTValueConverter<,>);
- elementType = actualTypeToConvert.GetGenericArguments()[1];
+ elementType = genericArgs[1];
}
else
{
{
if (typeToConvert == typeof(IDictionary))
{
- return s_IDictionaryConverter;
+ return s_converterForIDictionary;
}
converterType = typeof(JsonIDictionaryConverter<>);
{
if (typeToConvert == typeof(IList))
{
- return s_IListConverter;
+ return s_converterForIList;
}
converterType = typeof(JsonIListConverter<>);
Debug.Assert(typeof(IEnumerable).IsAssignableFrom(typeToConvert));
if (typeToConvert == typeof(IEnumerable))
{
- return s_IEnumerableConverter;
+ return s_converterForIEnumerable;
}
converterType = typeof(JsonIEnumerableConverter<>);
converter = (JsonConverter)Activator.CreateInstance(
genericType,
- BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
+ BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: null,
culture: null)!;
// 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;
+
namespace System.Text.Json.Serialization.Converters
{
/// <summary>
/// Default base class implementation of <cref>JsonIEnumerableConverter{TCollection, TElement}</cref>.
/// </summary>
- internal abstract class JsonIEnumerableDefaultConverter<TCollection, TElement> : JsonIEnumerableConverter<TCollection, TElement>
+ internal abstract class JsonIEnumerableDefaultConverter<TCollection, TElement> : JsonCollectionConverter<TCollection, TElement>
{
protected abstract void Add(TElement value, ref ReadStack state);
-
- protected virtual void CreateCollection(ref ReadStack state, JsonSerializerOptions options) { }
+ protected abstract void CreateCollection(ref ReadStack state, JsonSerializerOptions options);
protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }
protected static JsonConverter<TElement> GetElementConverter(ref ReadStack state)
{
- JsonConverter<TElement>? converter = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase as JsonConverter<TElement>;
- if (converter == null)
- {
- state.Current.JsonClassInfo.ElementClassInfo.PolicyProperty.ThrowCollectionNotSupportedException();
- }
+ JsonConverter<TElement> converter = (JsonConverter<TElement>)state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
+ Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
- return converter!;
+ return converter;
}
protected static JsonConverter<TElement> GetElementConverter(ref WriteStack state)
{
- JsonConverter<TElement>? converter = state.Current.DeclaredJsonPropertyInfo.ConverterBase as JsonConverter<TElement>;
- if (converter == null)
- {
- state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ThrowCollectionNotSupportedException();
- }
+ JsonConverter<TElement> converter = (JsonConverter<TElement>)state.Current.DeclaredJsonPropertyInfo!.ConverterBase;
+ Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
- return converter!;
+ return converter;
}
internal override bool OnTryRead(
// Fast path that avoids validation and extra indirection.
while (true)
{
- reader.Read();
+ reader.ReadWithVerify();
if (reader.TokenType == JsonTokenType.EndArray)
{
break;
}
else
{
+ // Process all elements.
while (true)
{
- reader.Read();
+ reader.ReadWithVerify();
if (reader.TokenType == JsonTokenType.EndArray)
{
break;
}
- // Obtain the CLR value from the JSON and apply to the object.
+ // Get the value from the converter and add it.
elementConverter.TryRead(ref reader, typeof(TElement), options, ref state, out TElement element);
Add(element, ref state);
}
}
else
{
- if (state.Current.ObjectState < StackFrameObjectState.StartToken)
+ // Slower path that supports continuation and preserved references.
+
+ if (state.Current.ObjectState == StackFrameObjectState.None)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
- state.Current.ObjectState = StackFrameObjectState.MetataPropertyValue;
+ state.Current.ObjectState = StackFrameObjectState.MetadataPropertyValue;
}
else if (shouldReadPreservedReferences)
{
}
// Handle the metadata properties.
- if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetataPropertyValue)
+ if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetadataPropertyValue)
{
- if (this.ResolveMetadata(ref reader, ref state, out value))
+ if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
{
if (state.Current.ObjectState == StackFrameObjectState.MetadataRefPropertyEndObject)
{
+ value = (TCollection)state.Current.ReturnValue!;
return true;
}
}
else
{
+ value = default!;
return false;
}
}
value = (TCollection)state.Current.ReturnValue!;
if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, value))
{
- // Reset so JsonPath throws exception with $id in it.
- state.Current.MetadataPropertyName = MetadataPropertyName.Id;
-
- ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId);
+ ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
}
}
{
JsonConverter<TElement> elementConverter = GetElementConverter(ref state);
- // Main loop for processing elements.
+ // Process all elements.
while (true)
{
if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
{
if (reader.TokenType == JsonTokenType.EndArray)
{
- // Clear the MetadataPropertyName in case we were processing $values since
- // we are not longer processing $values.
- state.Current.MetadataPropertyName = MetadataPropertyName.NoMetadata;
-
break;
}
if (state.Current.PropertyState < StackFramePropertyState.TryRead)
{
- // Obtain the CLR value from the JSON and apply to the object.
+ // Get the value from the converter and add it.
if (!elementConverter.TryRead(ref reader, typeof(TElement), options, ref state, out TElement element))
{
value = default!;
}
Add(element, ref state);
+
// No need to set PropertyState to TryRead since we're done with this element now.
state.Current.EndElement();
}
if (reader.TokenType != JsonTokenType.EndObject)
{
- ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(typeToConvert, reader, ref state);
+ if (reader.TokenType == JsonTokenType.PropertyName)
+ {
+ state.Current.JsonPropertyName = reader.GetSpan().ToArray();
+ }
+
+ ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(typeToConvert, reader);
}
}
}
{
if (reader.TokenType != JsonTokenType.EndObject)
{
- ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(typeToConvert, reader, ref state);
+ ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(typeToConvert, reader);
}
}
}
return true;
}
- internal override sealed bool OnTryWrite(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
+ internal sealed override bool OnTryWrite(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
{
bool success;
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is List<TElement>);
((List<TElement>)state.Current.ReturnValue!).Add(value);
}
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
// See the LICENSE file in the project root for more information.
using System.Collections;
-using System.Collections.Concurrent;
using System.Diagnostics;
-using System.Reflection;
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class JsonIEnumerableWithAddMethodConverter<TCollection> : JsonIEnumerableDefaultConverter<TCollection, object>
+ internal sealed class JsonIEnumerableWithAddMethodConverter<TCollection> :
+ JsonIEnumerableDefaultConverter<TCollection, object?>
where TCollection : IEnumerable
{
- protected override void Add(object value, ref ReadStack state)
+ protected override void Add(object? value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is TCollection);
Debug.Assert(state.Current.AddMethodDelegate != null);
- ((Action<TCollection, object>)state.Current.AddMethodDelegate)((TCollection)state.Current.ReturnValue!, value);
+ ((Action<TCollection, object?>)state.Current.AddMethodDelegate)((TCollection)state.Current.ReturnValue!, value);
}
protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
{
- JsonClassInfo classInfo = state.Current.JsonClassInfo;
+ JsonClassInfo.ConstructorDelegate? constructorDelegate = state.Current.JsonClassInfo.CreateObject;
- if (classInfo.CreateObject == null)
+ if (constructorDelegate == null)
{
- ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(classInfo.Type);
+ ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- state.Current.ReturnValue = classInfo.CreateObject()!;
- state.Current.AddMethodDelegate = GetOrAddEnumerableAddMethodDelegate(classInfo.Type, options);
+ state.Current.ReturnValue = constructorDelegate();
+ state.Current.AddMethodDelegate = GetAddMethodDelegate(options);
}
protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
enumerator = state.Current.CollectionEnumerator;
}
- JsonConverter<object> converter = GetElementConverter(ref state);
+ JsonConverter<object?> converter = GetElementConverter(ref state);
do
{
if (ShouldFlush(writer, ref state))
return false;
}
- if (!converter.TryWrite(writer, enumerator.Current!, options, ref state))
+ if (!converter.TryWrite(writer, enumerator.Current, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
}
- internal override Type RuntimeType => TypeToConvert;
-
- private readonly ConcurrentDictionary<Type, Action<TCollection, object>> _delegates = new ConcurrentDictionary<Type, Action<TCollection, object>>();
+ private Action<TCollection, object?>? _addMethodDelegate;
- internal Action<TCollection, object> GetOrAddEnumerableAddMethodDelegate(Type type, JsonSerializerOptions options)
+ internal Action<TCollection, object?> GetAddMethodDelegate(JsonSerializerOptions options)
{
- if (!_delegates.TryGetValue(type, out Action<TCollection, object>? result))
+ if (_addMethodDelegate == null)
{
// We verified this exists when we created the converter in the enumerable converter factory.
- result = options.MemberAccessorStrategy.CreateAddMethodDelegate<TCollection>();
+ _addMethodDelegate = options.MemberAccessorStrategy.CreateAddMethodDelegate<TCollection>();
}
- return result;
+ return _addMethodDelegate;
}
}
}
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
internal sealed class JsonIListConverter<TCollection> : JsonIEnumerableDefaultConverter<TCollection, object>
where TCollection : IList
{
- protected override void Add(object value, ref ReadStack state)
+ protected override void Add(object? value, ref ReadStack state)
{
- ((IList)state.Current.ReturnValue!).Add(value);
+ Debug.Assert(state.Current.ReturnValue is IList);
+ ((IList)state.Current.ReturnValue).Add(value);
}
protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
{
JsonClassInfo classInfo = state.Current.JsonClassInfo;
- if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+ if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- state.Current.ReturnValue = new List<object>();
+ state.Current.ReturnValue = new List<object?>();
}
else
{
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- else
- {
- TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
- if (returnValue.IsReadOnly)
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
- }
+ TCollection returnValue = (TCollection)classInfo.CreateObject()!;
- state.Current.ReturnValue = returnValue;
+ if (returnValue.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
}
+
+ state.Current.ReturnValue = returnValue;
}
}
enumerator = state.Current.CollectionEnumerator;
}
- JsonConverter<object> converter = JsonSerializerOptions.GetObjectConverter();
+ JsonConverter<object> converter = GetElementConverter(ref state);
do
{
if (ShouldFlush(writer, ref state))
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
{
if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface)
{
- return typeof(List<object>);
+ return typeof(List<object?>);
}
return TypeToConvert;
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is TCollection);
((TCollection)state.Current.ReturnValue!).Add(value);
}
{
JsonClassInfo classInfo = state.Current.JsonClassInfo;
- if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+ if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- else
- {
- TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
- if (returnValue.IsReadOnly)
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
- }
+ TCollection returnValue = (TCollection)classInfo.CreateObject()!;
- state.Current.ReturnValue = returnValue;
+ if (returnValue.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
}
+
+ state.Current.ReturnValue = returnValue;
}
}
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
{
- string key = state.Current.KeyName!;
+ Debug.Assert(state.Current.ReturnValue is Dictionary<string, TValue>);
+
+ string key = state.Current.JsonPropertyNameAsString!;
((Dictionary<string, TValue>)state.Current.ReturnValue!)[key] = value;
}
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is Dictionary<string, TValue>.Enumerator);
enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
}
JsonConverter<TValue> converter = GetValueConverter(ref state);
do
{
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
string key = GetKeyName(enumerator.Current.Key, ref state, options);
writer.WritePropertyName(key);
return false;
}
- state.Current.EndElement();
+ state.Current.EndDictionaryElement();
} while (enumerator.MoveNext());
return true;
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is TCollection);
((TCollection)state.Current.ReturnValue!).Add(value);
}
{
JsonClassInfo classInfo = state.Current.JsonClassInfo;
- if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+ if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
}
- else
- {
- TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
- if (returnValue.IsReadOnly)
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
- }
+ TCollection returnValue = (TCollection)classInfo.CreateObject()!;
- state.Current.ReturnValue = returnValue;
+ if (returnValue.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
}
+
+ state.Current.ReturnValue = returnValue;
}
}
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
-using System.Collections.Concurrent;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
{
- string key = state.Current.KeyName!;
+ Debug.Assert(state.Current.ReturnValue is Dictionary<string, TValue>);
+
+ string key = state.Current.JsonPropertyNameAsString!;
((Dictionary<string, TValue>)state.Current.ReturnValue!)[key] = value;
}
- internal override bool CanHaveMetadata => false;
+ internal override bool CanHaveIdMetadata => false;
protected override void CreateCollection(ref ReadStack state)
{
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is Dictionary<string, TValue>.Enumerator);
enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
}
JsonConverter<TValue> converter = GetValueConverter(ref state);
do
{
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
string key = GetKeyName(enumerator.Current.Key, ref state, options);
writer.WritePropertyName(key);
return false;
}
- state.Current.EndElement();
+ state.Current.EndDictionaryElement();
} while (enumerator.MoveNext());
return true;
}
- internal override Type RuntimeType => TypeToConvert;
-
private Func<IEnumerable<KeyValuePair<string, TValue>>, TCollection>? _creatorDelegate;
private Func<IEnumerable<KeyValuePair<string, TValue>>, TCollection> GetCreatorDelegate(JsonSerializerOptions options)
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
-using System.Collections.Concurrent;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is List<TElement>);
((List<TElement>)state.Current.ReturnValue!).Add(value);
}
- internal override bool CanHaveMetadata => false;
+ internal override bool CanHaveIdMetadata => false;
protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
{
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
}
- internal override Type RuntimeType => TypeToConvert;
-
private Func<IEnumerable<TElement>, TCollection>? _creatorDelegate;
private Func<IEnumerable<TElement>, TCollection> GetCreatorDelegate(JsonSerializerOptions options)
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is TCollection);
((TCollection)state.Current.ReturnValue!).Add(value);
}
{
if (state.Current.JsonClassInfo.CreateObject == null)
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
}
state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
{
List<TElement> list = value;
+
+ // Using an index is 2x faster than using an enumerator.
int index = state.Current.EnumeratorIndex;
JsonConverter<TElement> elementConverter = GetElementConverter(ref state);
state.Current.EnumeratorIndex = ++index;
return false;
}
-
- state.Current.EndElement();
}
}
// See the LICENSE file in the project root for more information.
using System.Collections;
+using System.Diagnostics;
using System.Reflection;
namespace System.Text.Json.Serialization.Converters
/// <summary>
/// Converter factory for all object-based types (non-enumerable and non-primitive).
/// </summary>
- internal class JsonObjectFactoryConverter : JsonConverterFactory
+ internal class JsonObjectConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
- // If the IEnumerableConverterFactory doesn't support the collection, ObjectConverterFactory doesn't either.
- return !typeof(IEnumerable).IsAssignableFrom(typeToConvert);
+ // This is the last built-in factory converter, so if the IEnumerableConverterFactory doesn't
+ // support it, then it is not IEnumerable.
+ Debug.Assert(!typeof(IEnumerable).IsAssignableFrom(typeToConvert));
+ return true;
}
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
obj = state.Current.JsonClassInfo.CreateObject!()!;
- // Read all properties.
+ // Process all properties.
while (true)
{
// Read the property name or EndObject.
- reader.Read();
+ reader.ReadWithVerify();
JsonTokenType tokenType = reader.TokenType;
if (tokenType == JsonTokenType.EndObject)
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}
- JsonSerializer.LookupProperty(
+ JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty(
obj,
ref reader,
options,
ref state,
- out JsonPropertyInfo jsonPropertyInfo,
out bool useExtensionProperty);
// Skip the property if not found.
if (!jsonPropertyInfo.ShouldDeserialize)
{
- reader.TrySkip();
+ reader.Skip();
+ state.Current.EndProperty();
continue;
}
// Set the property value.
- reader.Read();
+ reader.ReadWithVerify();
if (!useExtensionProperty)
{
{
jsonPropertyInfo.ReadJsonAndAddExtensionProperty(obj, ref state, ref reader);
}
+
+ // Ensure any exception thrown in the next read does not have a property in its JsonPath.
+ state.Current.EndProperty();
}
}
else
{
- if (state.Current.ObjectState < StackFrameObjectState.StartToken)
+ // Slower path that supports continuation and preserved references.
+
+ if (state.Current.ObjectState == StackFrameObjectState.None)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
}
// Handle the metadata properties.
- if (state.Current.ObjectState < StackFrameObjectState.MetataPropertyValue)
+ if (state.Current.ObjectState < StackFrameObjectState.MetadataPropertyValue)
{
if (shouldReadPreservedReferences)
{
- if (this.ResolveMetadata(ref reader, ref state, out value))
+ if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
{
if (state.Current.ObjectState == StackFrameObjectState.MetadataRefPropertyEndObject)
{
- if (!CanHaveMetadata)
- {
- ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(TypeToConvert);
- }
-
+ value = (T)state.Current.ReturnValue!;
return true;
}
}
else
{
+ value = default!;
return false;
}
}
- state.Current.ObjectState = StackFrameObjectState.MetataPropertyValue;
+ state.Current.ObjectState = StackFrameObjectState.MetadataPropertyValue;
}
if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
{
if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, obj))
{
- // Reset so JsonPath throws exception with $id in it.
- state.Current.MetadataPropertyName = MetadataPropertyName.Id;
-
- ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId);
+ ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
}
}
Debug.Assert(obj != null);
}
- // Read all properties.
+ // Process all properties.
while (true)
{
// Determine the property.
- if (state.Current.PropertyState < StackFramePropertyState.ReadName)
+ if (state.Current.PropertyState == StackFramePropertyState.None)
{
state.Current.PropertyState = StackFramePropertyState.ReadName;
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}
- JsonSerializer.LookupProperty(
+ jsonPropertyInfo = JsonSerializer.LookupProperty(
obj,
ref reader,
options,
ref state,
- out jsonPropertyInfo,
out bool useExtensionProperty);
state.Current.UseExtensionProperty = useExtensionProperty;
}
else
{
- jsonPropertyInfo = state.Current.JsonPropertyInfo;
+ Debug.Assert(state.Current.JsonPropertyInfo != null);
+ jsonPropertyInfo = state.Current.JsonPropertyInfo!;
}
if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
{
if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
{
- writer.WriteEndObject();
return true;
}
}
JsonPropertyInfo? dataExtensionProperty = state.Current.JsonClassInfo.DataExtensionProperty;
- int propertyCount;
+ int propertyCount = 0;
JsonPropertyInfo[]? propertyCacheArray = state.Current.JsonClassInfo.PropertyCacheArray;
if (propertyCacheArray != null)
{
propertyCount = propertyCacheArray.Length;
}
- else
- {
- propertyCount = 0;
- }
for (int i = 0; i < propertyCount; i++)
{
{
if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
{
- writer.WriteEndObject();
return true;
}
}
JsonPropertyInfo? dataExtensionProperty = state.Current.JsonClassInfo.DataExtensionProperty;
- int propertyCount;
+ int propertyCount = 0;
JsonPropertyInfo[]? propertyCacheArray = state.Current.JsonClassInfo.PropertyCacheArray;
if (propertyCacheArray != null)
{
propertyCount = propertyCacheArray.Length;
}
- else
- {
- propertyCount = 0;
- }
while (propertyCount > state.Current.EnumeratorIndex)
{
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is TCollection);
((TCollection)state.Current.ReturnValue!).Enqueue(value);
}
{
if (state.Current.JsonClassInfo.CreateObject == null)
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
}
state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
}
-
- internal override Type RuntimeType => typeof(Queue<TElement>);
}
}
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Text.Json.Serialization.Converters
{
{
protected override void Add(TElement value, ref ReadStack state)
{
+ Debug.Assert(state.Current.ReturnValue is TCollection);
((TCollection)state.Current.ReturnValue!).Push(value);
}
{
if (state.Current.JsonClassInfo.CreateObject == null)
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
}
state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
}
else
{
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}
state.Current.CollectionEnumerator = enumerator;
return false;
}
-
- state.Current.EndElement();
} while (enumerator.MoveNext());
return true;
}
-
- internal override Type RuntimeType => TypeToConvert;
}
}
bool valueSet = false;
// Get the first property.
- reader.Read();
+ reader.ReadWithVerify();
if (reader.TokenType != JsonTokenType.PropertyName)
{
ThrowHelper.ThrowJsonException();
string propertyName = reader.GetString()!;
if (propertyName == KeyName)
{
- reader.Read();
+ reader.ReadWithVerify();
k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, KeyName);
keySet = true;
}
else if (propertyName == ValueName)
{
- reader.Read();
+ reader.ReadWithVerify();
v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, ValueName);
valueSet = true;
}
}
// Get the second property.
- reader.Read();
+ reader.ReadWithVerify();
if (reader.TokenType != JsonTokenType.PropertyName)
{
ThrowHelper.ThrowJsonException();
propertyName = reader.GetString()!;
if (propertyName == KeyName)
{
- reader.Read();
+ reader.ReadWithVerify();
k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, KeyName);
keySet = true;
}
else if (propertyName == ValueName)
{
- reader.Read();
+ reader.ReadWithVerify();
v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, ValueName);
valueSet = true;
}
ThrowHelper.ThrowJsonException();
}
- reader.Read();
+ reader.ReadWithVerify();
if (reader.TokenType != JsonTokenType.EndObject)
{
{
// It is possible to cache the underlying converter since this is an internal converter and
// an instance is created only once for each JsonSerializerOptions instance.
- private JsonConverter<T> _converter;
+ private readonly JsonConverter<T> _converter;
public JsonValueConverterNullable(JsonConverter<T> converter)
{
// 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;
using System.Reflection;
namespace System.Text.Json.Serialization
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
+ Debug.Assert(typeToConvert.GetGenericArguments().Length > 0);
+
Type valueTypeToConvert = typeToConvert.GetGenericArguments()[0];
JsonConverter? valueConverter = options.GetConverter(valueTypeToConvert);
if (valueConverter == null)
{
- // todo: add test for this
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(valueTypeToConvert);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(valueTypeToConvert);
}
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
// 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.Buffers;
using System.Diagnostics;
using System.Reflection;
-namespace System.Text.Json
+namespace System.Text.Json.Serialization
{
internal static class ExtensionMethods
{
Debug.Assert(!baseType.IsInterface);
Debug.Assert(baseType == baseType.GetGenericTypeDefinition());
- Type baseTypeToCheck = type;
+ Type? baseTypeToCheck = type;
while (baseTypeToCheck != null && baseTypeToCheck != typeof(object))
{
}
}
- baseTypeToCheck = baseTypeToCheck.BaseType!;
+ baseTypeToCheck = baseTypeToCheck.BaseType;
}
return null;
return null;
}
- internal static Type? GetCompatibleNonGenericBaseClass(this Type type, string baseTypeAssemblyQualifiedNamePrefix)
- {
- Type baseTypeToCheck = type;
-
- while (baseTypeToCheck != null && baseTypeToCheck != typeof(object))
- {
- if (!baseTypeToCheck.IsGenericType)
- {
- Type nonGenericTypeToCheck = baseTypeToCheck;
- if (nonGenericTypeToCheck.AssemblyQualifiedName!.StartsWith(baseTypeAssemblyQualifiedNamePrefix))
- {
- return baseTypeToCheck;
- }
- }
-
- baseTypeToCheck = baseTypeToCheck.BaseType!;
- }
-
- return null;
- }
-
public static bool IsImmutableDictionaryType(this Type type)
{
if (!type.IsGenericType || !type.Assembly.FullName!.StartsWith("System.Collections.Immutable,"))
}
}
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
return null!;
}
}
}
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
return null!;
}
public static bool IsNonGenericStackOrQueue(this Type type)
{
- return type.GetCompatibleNonGenericBaseClass("System.Collections.Stack, System.Collections.NonGeneric") != null ||
- type.GetCompatibleNonGenericBaseClass("System.Collections.Queue, System.Collections.NonGeneric") != null;
+ Type? typeOfStack = Type.GetType("System.Collections.Stack, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
+ Type? typeOfQueue = Type.GetType("System.Collections.Queue, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
+
+ Debug.Assert(typeOfStack != null);
+ Debug.Assert(typeOfQueue != null);
+
+ return typeOfStack.IsAssignableFrom(type) || typeOfQueue.IsAssignableFrom(type);
}
}
}
if (converter == null)
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(propertyType, parentClassType, propertyInfo);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(propertyType, parentClassType, propertyInfo);
}
return CreateProperty(
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Converters;
namespace System.Text.Json
{
public delegate object? ConstructorDelegate();
public ConstructorDelegate? CreateObject { get; private set; }
- public delegate TCollection ConstructorDelegate<TCollection>(ICollection elements);
- public delegate TCollection ConstructorDelegate<TCollection, TElement>(IEnumerable<TElement> elements);
-
public ClassType ClassType { get; private set; }
public JsonPropertyInfo? DataExtensionProperty { get; private set; }
{
Type = type;
Options = options;
- JsonConverter? converter;
ClassType = GetClassType(
type,
propertyInfo: null,
out Type? runtimeType,
out Type? elementType,
- out converter,
+ out JsonConverter? converter,
options);
switch (ClassType)
break;
case ClassType.Invalid:
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(type);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
}
break;
default:
if (jsonPropertyInfo != null)
{
Type declaredPropertyType = jsonPropertyInfo.DeclaredPropertyType;
- if (typeof(Dictionary<string, object>).IsAssignableFrom(declaredPropertyType) ||
- typeof(Dictionary<string, JsonElement>).IsAssignableFrom(declaredPropertyType) ||
- typeof(IDictionary<string, object>).IsAssignableFrom(declaredPropertyType) ||
+ if (typeof(IDictionary<string, object>).IsAssignableFrom(declaredPropertyType) ||
typeof(IDictionary<string, JsonElement>).IsAssignableFrom(declaredPropertyType))
{
JsonConverter? converter = Options.GetConverter(declaredPropertyType);
if (converter == null)
{
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(declaredPropertyType);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(declaredPropertyType);
}
}
else
}
DataExtensionProperty = jsonPropertyInfo;
- jsonPropertyInfo.EscapedName = null;
return true;
}
{
runtimeType = converterRuntimeType;
}
- else if (converterRuntimeType.IsAssignableFrom(type))
- {
- runtimeType = type;
- }
- else if (converter.TypeToConvert.IsAssignableFrom(type))
+ else if (converterRuntimeType.IsAssignableFrom(type) || converter.TypeToConvert.IsAssignableFrom(type))
{
runtimeType = type;
}
else
{
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(type, parentClassType, propertyInfo);
+ throw ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type, parentClassType, propertyInfo);
}
}
}
namespace System.Text.Json.Serialization
{
/// <summary>
- /// Base class for IEnumerable-based collections.
+ /// Base class for all collections. Collections are assumed to implement <cref>System.Collections.IEnumerable</cref>.
/// </summary>
- internal abstract class JsonIEnumerableConverter<TCollection, TElement> : JsonResumableConverter<TCollection>
+ internal abstract class JsonCollectionConverter<TCollection, TElement> : JsonResumableConverter<TCollection>
{
- private Type _elementType = typeof(TElement);
-
- internal override bool CanHaveValuesMetadata => true;
internal override ClassType ClassType => ClassType.Enumerable;
- internal override Type ElementType => _elementType;
+ internal override Type ElementType => typeof(TElement);
}
}
/// </summary>
public abstract partial class JsonConverter
{
+ /// <summary>
+ /// Perform a Read() and if read-ahead is required, also read-ahead (to the end of the current JSON level).
+ /// </summary>
// AggressiveInlining used since this method is on a hot path and short. The optionally called
// method DoSingleValueReadWithReadAhead is not inlined.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool DoSingleValueReadWithReadAhead(ref Utf8JsonReader reader, ref ReadStack state)
{
- // When we're reading ahead we always have to save the state
- // as we don't know if the next token is an opening object or
- // array brace.
- state.InitialReaderState = reader.CurrentState;
- state.InitialReaderBytesConsumed = reader.BytesConsumed;
+ // When we're reading ahead we always have to save the state as we don't know if the next token
+ // is an opening object or an array brace.
+ JsonReaderState initialReaderState = reader.CurrentState;
+ long initialReaderBytesConsumed = reader.BytesConsumed;
if (!reader.Read())
{
bool complete = reader.TrySkip();
// We need to restore the state in all cases as we need to be positioned back before
- // the current token to either attempt to skip again or to actually read the value in
- // HandleValue below.
+ // the current token to either attempt to skip again or to actually read the value.
- reader = new Utf8JsonReader(reader.OriginalSpan.Slice(checked((int)state.InitialReaderBytesConsumed)),
+ reader = new Utf8JsonReader(reader.OriginalSpan.Slice(checked((int)initialReaderBytesConsumed)),
isFinalBlock: reader.IsFinalBlock,
- state: state.InitialReaderState);
+ state: initialReaderState);
Debug.Assert(reader.BytesConsumed == 0);
- state.BytesConsumed += state.InitialReaderBytesConsumed;
+ state.BytesConsumed += initialReaderBytesConsumed;
if (!complete)
{
return false;
}
- // Success, requeue the reader to the token for HandleValue.
- reader.Read();
+ // Success, requeue the reader to the start token.
+ reader.ReadWithVerify();
Debug.Assert(tokenType == reader.TokenType);
}
internal bool CanUseDirectReadOrWrite { get; set; }
/// <summary>
- /// Can the converter have any metadata (properties starting with $).
+ /// Can the converter have $id metadata.
/// </summary>
- internal virtual bool CanHaveMetadata => !TypeToConvert.IsValueType;
+ internal virtual bool CanHaveIdMetadata => true;
internal bool CanBePolymorphic { get; set; }
- /// <summary>
- /// Can the converter have $values metadata.
- /// </summary>
- internal virtual bool CanHaveValuesMetadata => false;
-
internal abstract JsonPropertyInfo CreateJsonPropertyInfo();
internal abstract Type? ElementType { get; }
/// </summary>
protected JsonConverterFactory() { }
- internal override sealed ClassType ClassType
+ internal sealed override ClassType ClassType
{
get
{
internal override JsonPropertyInfo CreateJsonPropertyInfo()
{
- throw new NotSupportedException();
+ // We should never get here.
+ Debug.Assert(false);
+
+ throw new InvalidOperationException();
}
internal sealed override Type? ElementType => null;
- internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOptions options)
+ internal JsonConverter? GetConverterInternal(Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(CanConvert(typeToConvert));
- return CreateConverter(typeToConvert, options)!;
+ return CreateConverter(typeToConvert, options);
}
- internal override sealed bool TryReadAsObject(
+ internal sealed override bool TryReadAsObject(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options,
ref ReadStack state,
out object? value)
{
- throw new NotSupportedException();
+ // We should never get here.
+ Debug.Assert(false);
+
+ throw new InvalidOperationException();
}
- internal override sealed bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state)
+ internal sealed override bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state)
{
- throw new NotSupportedException();
+ // We should never get here.
+ Debug.Assert(false);
+
+ throw new InvalidOperationException();
}
- internal override sealed Type TypeToConvert => null!;
+ internal sealed override Type TypeToConvert => null!;
}
}
/// <typeparam name="T">The <see cref="Type"/> to convert.</typeparam>
public abstract class JsonConverter<T> : JsonConverter
{
- private Type _typeToConvert = typeof(T);
-
/// <summary>
/// When overidden, constructs a new <see cref="JsonConverter{T}"/> instance.
/// </summary>
internal override ClassType ClassType => ClassType.Value;
- internal override sealed JsonPropertyInfo CreateJsonPropertyInfo()
+ internal sealed override JsonPropertyInfo CreateJsonPropertyInfo()
{
return new JsonPropertyInfo<T>();
}
/// <summary>
/// Is the converter built-in.
/// </summary>
- internal bool IsInternalConverter;
+ internal bool IsInternalConverter { get; set; }
// This non-generic API is sealed as it just forwards to the generic version.
- internal override sealed bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state)
+ internal sealed override bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state)
{
T valueOfT = (T)value!;
return TryWrite(writer, valueOfT, options, ref state);
}
// This non-generic API is sealed as it just forwards to the generic version.
- internal override sealed bool TryReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out object? value)
+ internal sealed override bool TryReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out object? value)
{
bool success = TryRead(ref reader, typeToConvert, options, ref state, out T valueOfT);
if (success)
else
#endif
{
- state.Current.OriginalPropertyTokenType = reader.TokenType;
- state.Current.OriginalPropertyDepth = reader.CurrentDepth;
- state.Current.OriginalPropertyBytesConsumed = reader.BytesConsumed;
+ JsonTokenType originalPropertyTokenType = reader.TokenType;
+ int originalPropertyDepth = reader.CurrentDepth;
+ long originalPropertyBytesConsumed = reader.BytesConsumed;
value = Read(ref reader, typeToConvert, options);
VerifyRead(
- state.Current.OriginalPropertyTokenType,
- state.Current.OriginalPropertyDepth,
- state.Current.OriginalPropertyBytesConsumed != reader.BytesConsumed,
+ originalPropertyTokenType,
+ originalPropertyDepth,
+ originalPropertyBytesConsumed,
+ isValueConverter: true,
ref reader);
}
VerifyRead(
state.Current.OriginalTokenType,
state.Current.OriginalDepth,
- hasConsumedAnyBytes: true,
+ bytesConsumed : 0,
+ isValueConverter: false,
ref reader);
// No need to clear state.Current.* since a stack pop will occur.
if (type != TypeToConvert)
{
+ // Handle polymorphic case and get the new converter.
JsonConverter jsonConverter = state.Current.InitializeReEntry(type, options);
if (jsonConverter != this)
{
if (ClassType == ClassType.Value)
{
- if (!state.IsContinuation)
- {
- state.Current.OriginalPropertyDepth = writer.CurrentDepth;
- }
+ Debug.Assert(!state.IsContinuation);
+
+ int originalPropertyDepth = writer.CurrentDepth;
Write(writer, value, options);
- VerifyWrite(state.Current.OriginalPropertyDepth, writer);
+ VerifyWrite(originalPropertyDepth, writer);
return true;
}
if (ClassType == ClassType.Value)
{
- if (!state.IsContinuation)
- {
- state.Current.OriginalPropertyDepth = writer.CurrentDepth;
- }
+ Debug.Assert(!state.IsContinuation);
+
+ int originalPropertyDepth = writer.CurrentDepth;
// Ignore the naming policy for extension data.
state.Current.IgnoreDictionaryKeyPolicy = true;
success = dictionaryConverter.OnWriteResume(writer, value, options, ref state);
if (success)
{
- VerifyWrite(state.Current.OriginalPropertyDepth, writer);
+ VerifyWrite(originalPropertyDepth, writer);
}
}
else
return success;
}
- internal override sealed Type TypeToConvert => _typeToConvert;
+ internal sealed override Type TypeToConvert => typeof(T);
- internal void VerifyRead(JsonTokenType tokenType, int depth, bool hasConsumedAnyBytes, ref Utf8JsonReader reader)
+ internal void VerifyRead(JsonTokenType tokenType, int depth, long bytesConsumed, bool isValueConverter, ref Utf8JsonReader reader)
{
switch (tokenType)
{
break;
default:
- // A non-array or non-object should not make any additional reads.
- if (hasConsumedAnyBytes)
+ // A non-value converter (object or collection) should always have Start and End tokens.
+ // A value converter should not make any reads.
+ if (!isValueConverter || reader.BytesConsumed != bytesConsumed)
{
ThrowHelper.ThrowJsonException_SerializationConverterRead(this);
}
internal abstract class JsonObjectConverter<T> : JsonResumableConverter<T>
{
internal override ClassType ClassType => ClassType.Object;
- internal override sealed Type? ElementType => null;
+ internal sealed override Type? ElementType => null;
}
}
using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json.Serialization;
{
if (HasGetter)
{
- if (ConverterBase == null)
- {
- ThrowCollectionNotSupportedException();
- }
+ Debug.Assert(ConverterBase != null);
ShouldSerialize = true;
JsonConverter converter,
JsonSerializerOptions options)
{
+ Debug.Assert(converter != null);
+
ParentClassType = parentClassType;
DeclaredPropertyType = declaredPropertyType;
RuntimePropertyType = runtimePropertyType;
// The name of the property with any casing policy or the name specified from JsonPropertyNameAttribute.
public byte[]? Name { get; private set; }
- public string? NameAsString { get; set; }
+ public string? NameAsString { get; private set; }
// Key for fast property name lookup.
public ulong PropertyNameKey { get; set; }
public bool ReadJsonAndAddExtensionProperty(object obj, ref ReadStack state, ref Utf8JsonReader reader)
{
object propValue = GetValueAsObject(obj)!;
- IDictionary<string, object?>? dictionaryObject = propValue as IDictionary<string, object?>;
- if (dictionaryObject != null && reader.TokenType == JsonTokenType.Null)
+ if (propValue is IDictionary<string, object?> dictionaryObject)
{
- // A null JSON value is treated as a null object reference.
- dictionaryObject[state.Current.KeyName!] = null;
+ // Handle case where extension property is System.Object-based.
+
+ if (reader.TokenType == JsonTokenType.Null)
+ {
+ // A null JSON value is treated as a null object reference.
+ dictionaryObject[state.Current.JsonPropertyNameAsString!] = null;
+ }
+ else
+ {
+ JsonConverter<object> converter = (JsonConverter<object>)
+ state.Current.JsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
+
+ if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out object? value))
+ {
+ return false;
+ }
+
+ dictionaryObject[state.Current.JsonPropertyNameAsString!] = value;
+ }
}
else
{
- JsonConverter<JsonElement> converter = JsonSerializerOptions.GetJsonElementConverter();
- if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out JsonElement jsonElement))
+ // Handle case where extension property is JsonElement-based.
+
+ Debug.Assert(propValue is IDictionary<string, JsonElement>);
+ IDictionary<string, JsonElement> dictionaryJsonElement = (IDictionary<string, JsonElement>)propValue;
+
+ JsonConverter<JsonElement> converter = (JsonConverter<JsonElement>)
+ state.Current.JsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
+
+ if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out JsonElement value))
{
- // No need to set a partial object here since JsonElement is a struct that must be read in full.
return false;
}
- if (dictionaryObject != null)
- {
- dictionaryObject[state.Current.KeyName!] = jsonElement;
- }
- else
- {
- IDictionary<string, JsonElement> dictionaryJsonElement = (IDictionary<string, JsonElement>)propValue;
- dictionaryJsonElement[state.Current.KeyName!] = jsonElement;
- }
+ dictionaryJsonElement[state.Current.JsonPropertyNameAsString!] = value;
}
return true;
public bool ShouldSerialize { get; private set; }
public bool ShouldDeserialize { get; private set; }
-
- public void ThrowCollectionNotSupportedException()
- {
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(RuntimePropertyType!, ParentClassType, PropertyInfo);
- }
}
}
if (state.Current.PropertyState < StackFramePropertyState.Name)
{
state.Current.PropertyState = StackFramePropertyState.Name;
-
- Debug.Assert(EscapedName.HasValue);
writer.WritePropertyName(EscapedName.Value);
}
}
else
{
- state.Current.PolymorphicJsonPropertyInfo = state.Current.DeclaredJsonPropertyInfo.RuntimeClassInfo.ElementClassInfo!.PolicyProperty;
+ state.Current.PolymorphicJsonPropertyInfo = state.Current.DeclaredJsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PolicyProperty;
success = Converter.TryWriteDataExtensionProperty(writer, value, Options, ref state);
}
}
else
{
- // Optimize for internal converters by avoiding the extra call to TryRead.
+ // Get the value from the converter and set the property.
if (Converter.CanUseDirectReadOrWrite)
{
+ // Optimize for internal converters by avoiding the extra call to TryRead.
TConverter fastvalue = Converter.Read(ref reader, RuntimePropertyType!, Options);
if (!IgnoreNullValues || (!isNullToken && fastvalue != null))
{
// Bridge from resumable to value converters.
if (options == null)
{
- options = JsonSerializerOptions.s_defaultOptions;
+ throw new ArgumentNullException(nameof(options));
}
ReadStack state = default;
// Bridge from resumable to value converters.
if (options == null)
{
- options = JsonSerializerOptions.s_defaultOptions;
+ throw new ArgumentNullException(nameof(options));
}
WriteStack state = default;
- state.InitializeRoot(typeof(T), options);
+ state.InitializeRoot(typeof(T), options, supportContinuation: false);
TryWrite(writer, value, options, ref state);
}
}
{
public static partial class JsonSerializer
{
- internal static bool ResolveMetadata<T>(
- this JsonConverter converter,
+ /// <summary>
+ /// Returns true if successful, false is the reader ran out of buffer.
+ /// Sets state.Current.ReturnValue to the $ref target for MetadataRefProperty cases.
+ /// </summary>
+ internal static bool ResolveMetadata(
+ JsonConverter converter,
ref Utf8JsonReader reader,
- ref ReadStack state,
- out T value)
+ ref ReadStack state)
{
if (state.Current.ObjectState < StackFrameObjectState.MetadataPropertyName)
{
// Read the first metadata property name.
if (!reader.Read())
{
- value = default!;
return false;
}
if (reader.TokenType != JsonTokenType.PropertyName)
{
+ // An enumerable needs metadata since it starts with StartObject.
if (converter.ClassType == ClassType.Enumerable)
{
ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert);
}
- else
- {
- ThrowHelper.ThrowJsonException_MetadataIdIsNotFirstProperty();
- }
+
+ // The reader should have detected other invalid cases.
+ Debug.Assert(reader.TokenType == JsonTokenType.EndObject);
+
+ // Skip the read of the first property name, since we already read it above.
+ state.Current.PropertyState = StackFramePropertyState.ReadName;
+
+ return true;
}
- ReadOnlySpan<byte> propertyName = GetSpan(ref reader);
+ ReadOnlySpan<byte> propertyName = reader.GetSpan();
MetadataPropertyName metadata = GetMetadataPropertyName(propertyName);
- state.Current.MetadataPropertyName = metadata;
- if (metadata == MetadataPropertyName.Ref)
+ if (metadata == MetadataPropertyName.Id)
{
- if (!converter.CanHaveMetadata)
+ state.Current.JsonPropertyName = propertyName.ToArray();
+ if (!converter.CanHaveIdMetadata)
{
- ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader);
+ ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(converter.TypeToConvert);
}
- state.Current.ObjectState = StackFrameObjectState.MetadataRefProperty;
+ state.Current.ObjectState = StackFrameObjectState.MetadataIdProperty;
}
- else if (metadata == MetadataPropertyName.Id)
+ else if (metadata == MetadataPropertyName.Ref)
{
- if (!converter.CanHaveMetadata)
+ state.Current.JsonPropertyName = propertyName.ToArray();
+ if (converter.TypeToConvert.IsValueType)
{
- if ((converter.ClassType & (ClassType.Dictionary | ClassType.Enumerable)) != 0)
- {
- ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(converter.TypeToConvert);
- }
- else
- {
- ThrowHelper.ThrowJsonException_MetadataInvalidReferenceToValueType(state.Current.JsonClassInfo.Type);
- }
+ ThrowHelper.ThrowJsonException_MetadataInvalidReferenceToValueType(converter.TypeToConvert);
}
- state.Current.ObjectState = StackFrameObjectState.MetadataIdProperty;
+ state.Current.ObjectState = StackFrameObjectState.MetadataRefProperty;
}
else if (metadata == MetadataPropertyName.Values)
{
- ThrowHelper.ThrowJsonException_MetadataMissingIdBeforeValues();
+ state.Current.JsonPropertyName = propertyName.ToArray();
+ if (converter.ClassType == ClassType.Enumerable)
+ {
+ ThrowHelper.ThrowJsonException_MetadataMissingIdBeforeValues();
+ }
+ else
+ {
+ ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader);
+ }
}
else
{
// Having a StartObject without metadata properties is not allowed.
if (converter.ClassType == ClassType.Enumerable)
{
- ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(converter.TypeToConvert, reader, ref state);
+ state.Current.JsonPropertyName = propertyName.ToArray();
+ ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(converter.TypeToConvert, reader);
}
// Skip the read of the first property name, since we already read it above.
state.Current.PropertyState = StackFramePropertyState.ReadName;
- value = default!;
return true;
}
}
{
if (!reader.Read())
{
- value = default!;
return false;
}
string key = reader.GetString()!;
- state.Current.ReturnValue = state.ReferenceResolver.ResolveReferenceOnDeserialize(key!)!;
+ // todo: verify value is converter.TypeToConvert and throw JsonException? (currently no test)
+ state.Current.ReturnValue = state.ReferenceResolver.ResolveReferenceOnDeserialize(key);
state.Current.ObjectState = StackFrameObjectState.MetadataRefPropertyEndObject;
}
else if (state.Current.ObjectState == StackFrameObjectState.MetadataIdProperty)
{
if (!reader.Read())
{
- value = default!;
return false;
}
ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType);
}
- string id = reader.GetString()!;
- state.Current.MetadataId = id;
+ state.Current.MetadataId = reader.GetString();
// Clear the MetadataPropertyName since we are done processing Id.
- state.Current.MetadataPropertyName = MetadataPropertyName.NoMetadata;
+ state.Current.JsonPropertyName = default;
- if (converter.ClassType == ClassType.Enumerable && converter.CanHaveValuesMetadata)
+ if (converter.ClassType == ClassType.Enumerable)
{
// Need to Read $values property name.
state.Current.ObjectState = StackFrameObjectState.MetadataValuesPropertyName;
else
{
// We are done reading metadata.
- state.Current.ObjectState = StackFrameObjectState.MetataPropertyValue;
+ state.Current.ObjectState = StackFrameObjectState.MetadataPropertyValue;
+ return true;
}
}
{
if (!reader.Read())
{
- value = default!;
return false;
}
if (reader.TokenType != JsonTokenType.EndObject)
{
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(converter.TypeToConvert);
- }
+ // We just read a property. The only valid next tokens are EndObject and PropertyName.
+ Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
- // Clear the MetadataPropertyName since we are done processing Ref.
- state.Current.MetadataPropertyName = MetadataPropertyName.NoMetadata;
+ ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(reader.GetSpan(), ref state);
+ }
- value = (T)state.Current.ReturnValue!;
return true;
}
{
if (!reader.Read())
{
- value = default!;
return false;
}
ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert);
}
- if (reader.GetString() != "$values")
+ ReadOnlySpan<byte> propertyName = reader.GetSpan();
+
+ // Remember the property in case we get an exception.
+ state.Current.JsonPropertyName = propertyName.ToArray();
+
+ if (GetMetadataPropertyName(propertyName) != MetadataPropertyName.Values)
{
- ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert);
+ ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(converter.TypeToConvert, reader);
}
- state.Current.MetadataPropertyName = MetadataPropertyName.Values;
state.Current.ObjectState = StackFrameObjectState.MetadataValuesPropertyStartArray;
}
{
if (!reader.Read())
{
- value = default!;
return false;
}
if (reader.TokenType != JsonTokenType.StartArray)
{
- ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert);
+ ThrowHelper.ThrowJsonException_MetadataValuesInvalidToken(reader.TokenType);
}
- state.Current.ObjectState = StackFrameObjectState.MetataPropertyValue;
+ state.Current.ObjectState = StackFrameObjectState.MetadataPropertyValue;
}
- value = default!;
return true;
}
- internal static string? GetMetadataPropertyName(in ReadStackFrame frame)
- {
- switch (frame.MetadataPropertyName)
- {
- case MetadataPropertyName.Id:
- return "$id";
-
- case MetadataPropertyName.Ref:
- return "$ref";
-
- case MetadataPropertyName.Values:
- return "$values";
- }
-
- return null;
- }
-
internal static MetadataPropertyName GetMetadataPropertyName(ReadOnlySpan<byte> propertyName)
{
if (propertyName.Length > 0 && propertyName[0] == '$')
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
namespace System.Text.Json
{
public static partial class JsonSerializer
{
/// <summary>
- /// Lookup the property given its name in the reader.
+ /// Lookup the property given its name (obtained from the reader) and return it.
+ /// Also sets state.Current.JsonPropertyInfo to a non-null value.
/// </summary>
// AggressiveInlining used although a large method it is only called from two locations and is on a hot path.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static void LookupProperty(
+ internal static JsonPropertyInfo LookupProperty(
object obj,
ref Utf8JsonReader reader,
JsonSerializerOptions options,
ref ReadStack state,
- out JsonPropertyInfo jsonPropertyInfo,
out bool useExtensionProperty)
{
Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);
- ReadOnlySpan<byte> unescapedPropertyName = GetSpan(ref reader);
- ReadOnlySpan<byte> propertyName;
+ JsonPropertyInfo jsonPropertyInfo;
+
+ ReadOnlySpan<byte> unescapedPropertyName;
+ ReadOnlySpan<byte> propertyName = reader.GetSpan();
if (reader._stringHasEscaping)
{
- int idx = unescapedPropertyName.IndexOf(JsonConstants.BackSlash);
+ int idx = propertyName.IndexOf(JsonConstants.BackSlash);
Debug.Assert(idx != -1);
- propertyName = GetUnescapedString(unescapedPropertyName, idx);
+ unescapedPropertyName = GetUnescapedString(propertyName, idx);
}
else
{
- propertyName = unescapedPropertyName;
+ unescapedPropertyName = propertyName;
+ }
+
+ if (options.ReferenceHandling.ShouldReadPreservedReferences())
+ {
+ if (propertyName.Length > 0 && propertyName[0] == '$')
+ {
+ ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
+ }
}
- jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(propertyName, ref state.Current);
+ jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current);
// Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called.
state.Current.PropertyIndex++;
// Determine if we should use the extension property.
if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
{
- if (options.ReferenceHandling.ShouldReadPreservedReferences())
- {
- if (unescapedPropertyName.Length > 0 && unescapedPropertyName[0] == '$')
- {
- // Ensure JsonPath doesn't attempt to use the previous property.
- state.Current.JsonPropertyInfo = null!;
-
- ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(unescapedPropertyName, ref state, reader);
- }
- }
-
JsonPropertyInfo? dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty;
if (dataExtProperty != null)
{
- state.Current.JsonPropertyName = propertyName.ToArray();
- state.Current.KeyName = JsonHelpers.Utf8GetString(propertyName);
+ state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName);
CreateDataExtensionProperty(obj, dataExtProperty);
jsonPropertyInfo = dataExtProperty;
}
state.Current.JsonPropertyInfo = jsonPropertyInfo;
useExtensionProperty = true;
- return;
+ return jsonPropertyInfo;
}
// Support JsonException.Path.
Debug.Assert(
jsonPropertyInfo.JsonPropertyName == null ||
options.PropertyNameCaseInsensitive ||
- propertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName));
+ unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName));
state.Current.JsonPropertyInfo = jsonPropertyInfo;
if (jsonPropertyInfo.JsonPropertyName == null)
{
- byte[] propertyNameArray = propertyName.ToArray();
+ byte[] propertyNameArray = unescapedPropertyName.ToArray();
if (options.PropertyNameCaseInsensitive)
{
// Each payload can have a different name here; remember the value on the temporary stack.
state.Current.JsonPropertyInfo = jsonPropertyInfo;
useExtensionProperty = false;
+ return jsonPropertyInfo;
}
- internal static void CreateDataExtensionProperty(
+ private static void CreateDataExtensionProperty(
object obj,
JsonPropertyInfo jsonPropertyInfo)
{
// 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;
-using System.Runtime.CompilerServices;
-
namespace System.Text.Json
{
public static partial class JsonSerializer
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static ReadOnlySpan<byte> GetSpan(ref Utf8JsonReader reader)
- {
- ReadOnlySpan<byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
- return propertyName;
- }
-
private static object? ReadCore(
Type returnType,
JsonSerializerOptions options,
// to enable read ahead behaviors to ensure we have complete json objects and arrays
// ({}, []) when needed. (Notably to successfully parse JsonElement via JsonDocument
// to assign to object and JsonElement properties in the constructed .NET object.)
- //state.SetToTop();
state.ReadAhead = !isFinalBlock;
state.BytesConsumed = 0;
state.Current.InitializeReEntry(typeof(T), options, propertyName);
- return (T)ReadCoreReEntry(options, ref reader, ref state)!;
+ T value = (T)ReadCoreReEntry(options, ref reader, ref state)!;
+
+ // Clear the current property state since we are done processing it.
+ state.Current.EndProperty();
+
+ return value;
}
/// <summary>
valueSpan.CopyTo(rentedSpan);
}
- JsonReaderOptions originalReaderOptions = state.Options;
+ JsonReaderOptions originalReaderOptions = readerState.Options;
var newReader = new Utf8JsonReader(rentedSpan, originalReaderOptions);
/// </summary>
public static partial class JsonSerializer
{
- internal static void ReadCore(
+ private static void ReadCore(
JsonSerializerOptions options,
ref Utf8JsonReader reader,
ref ReadStack state)
}
else
{
- // For a continuation continue to read ahead here to avoid having to build and
- // then tear down the call stack if there is more than one buffer fetch necessary.
+ // For a continuation, read ahead here to avoid having to build and then tear
+ // down the call stack if there is more than one buffer fetch necessary.
if (!JsonConverter.SingleValueReadWithReadAhead(ClassType.Value, ref reader, ref state))
{
state.BytesConsumed += reader.BytesConsumed;
ref Utf8JsonReader reader,
ref ReadStack state)
{
- JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+ JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo!;
JsonConverter converter = jsonPropertyInfo.ConverterBase;
bool success = converter.TryReadAsObject(ref reader, jsonPropertyInfo.RuntimePropertyType!, options, ref state, out object? value);
Debug.Assert(success);
public static partial class JsonSerializer
{
// Pre-encoded metadata properties.
- private static readonly JsonEncodedText s_metadataId = JsonEncodedText.Encode("$id", encoder: null);
- private static readonly JsonEncodedText s_metadataRef = JsonEncodedText.Encode("$ref", encoder: null);
- private static readonly JsonEncodedText s_metadataValues = JsonEncodedText.Encode("$values", encoder: null);
+ internal static readonly JsonEncodedText s_metadataId = JsonEncodedText.Encode("$id", encoder: null);
+ internal static readonly JsonEncodedText s_metadataRef = JsonEncodedText.Encode("$ref", encoder: null);
+ internal static readonly JsonEncodedText s_metadataValues = JsonEncodedText.Encode("$values", encoder: null);
+
+ internal static MetadataPropertyName GetResolvedReferenceHandling(
+ JsonConverter converter,
+ object value,
+ ref WriteStack state,
+ out string? referenceId)
+ {
+ if (!converter.CanHaveIdMetadata || converter.TypeToConvert.IsValueType)
+ {
+ referenceId = default;
+ return MetadataPropertyName.NoMetadata;
+ }
+
+ if (state.ReferenceResolver.TryGetOrAddReferenceOnSerialize(value, out referenceId))
+ {
+ return MetadataPropertyName.Ref;
+ }
+
+ return MetadataPropertyName.Id;
+ }
internal static MetadataPropertyName WriteReferenceForObject(
JsonConverter jsonConverter,
ref WriteStack state,
Utf8JsonWriter writer)
{
- MetadataPropertyName metadataToWrite = state.GetResolvedReferenceHandling(jsonConverter, currentValue, out string? referenceId);
+ MetadataPropertyName metadataToWrite = GetResolvedReferenceHandling(jsonConverter, currentValue, ref state, out string? referenceId);
if (metadataToWrite == MetadataPropertyName.Ref)
{
writer.WriteString(s_metadataRef, referenceId!);
+ writer.WriteEndObject();
}
else if (metadataToWrite == MetadataPropertyName.Id)
{
ref WriteStack state,
Utf8JsonWriter writer)
{
- MetadataPropertyName metadataToWrite = state.GetResolvedReferenceHandling(jsonConverter, currentValue, out string? referenceId);
+ MetadataPropertyName metadataToWrite = GetResolvedReferenceHandling(jsonConverter, currentValue, ref state, out string? referenceId);
if (metadataToWrite == MetadataPropertyName.NoMetadata)
{
{
writer.WriteStartObject();
writer.WriteString(s_metadataId, referenceId!);
- writer.WritePropertyName(s_metadataValues);
- writer.WriteStartArray();
+ writer.WriteStartArray(s_metadataValues);
}
else
{
}
WriteStack state = default;
- state.InitializeRoot(type!, options);
+ state.InitializeRoot(type!, options, supportContinuation: false);
WriteCore(writer, value, options, ref state, state.Current.JsonClassInfo!.PolicyProperty!.ConverterBase);
}
}
WriteStack state = default;
- state.InitializeRoot(inputType, options);
-
- // Ensures converters support contination due to having to re-populate the buffer from a Stream.
- state.SupportContinuation = true;
+ state.InitializeRoot(inputType, options, supportContinuation: true);
bool isFinalBlock;
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
+using System.Diagnostics;
namespace System.Text.Json
{
public static partial class JsonSerializer
{
/// <summary>
- /// Internal version that allows re-entry with preserving ReadStack so that JsonPath works correctly.
+ /// Internal version that allows re-entry with preserving WriteStack so that JsonPath works correctly.
/// </summary>
+ // If this is made public, we will also want to have a non-generic version.
internal static void Serialize<T>(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state, string? propertyName = null)
{
if (options == null)
}
JsonConverter jsonConverter = state.Current.InitializeReEntry(typeof(T), options, propertyName);
- Write(writer, value, options, ref state, jsonConverter);
- }
-
- /// <summary>
- /// Internal version that allows re-entry with preserving ReadStack so that JsonPath works correctly.
- /// </summary>
- internal static void Serialize(Utf8JsonWriter writer, object? value, Type inputType, JsonSerializerOptions options, ref WriteStack state, string? propertyName = null)
- {
- if (inputType == null)
- {
- throw new ArgumentNullException(nameof(inputType));
- }
-
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
- JsonConverter jsonConverter = state.Current.InitializeReEntry(inputType, options, propertyName);
- Write(writer, value, options, ref state, jsonConverter);
+ bool success = jsonConverter.TryWriteAsObject(writer, value, options, ref state);
+ Debug.Assert(success);
}
/// <summary>
{
try
{
- return Write(writer, value, options, ref state, jsonConverter);
+ return jsonConverter.TryWriteAsObject(writer, value, options, ref state);
}
catch (InvalidOperationException ex) when (ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException)
{
ThrowHelper.ReThrowWithPath(state, ex);
- return default;
+ throw;
}
catch (JsonException ex)
{
throw;
}
}
-
- private static bool Write(
- Utf8JsonWriter writer,
- object? value,
- JsonSerializerOptions options,
- ref WriteStack state,
- JsonConverter jsonConverter)
- {
- return jsonConverter.TryWriteAsObject(writer, value, options, ref state);
- }
}
}
converters.Add(new JsonConverterEnum());
converters.Add(new JsonKeyValuePairConverter());
- // Enumerable and Object converters should always be last since they can convert any IEnumerable
- // or non-IEnumerable.
+ // IEnumerable should always be last since they can convert any IEnumerable.
converters.Add(new JsonIEnumerableConverterFactory());
- converters.Add(new JsonObjectFactoryConverter());
+
+ // Object should always be last since it converts any type.
+ converters.Add(new JsonObjectConverterFactory());
Debug.Assert(NumberOfConverters == converters.Count);
return converter;
}
-
private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converterAttribute, Type typeToConvert, Type classTypeAttributeIsOn, PropertyInfo? propertyInfo)
{
JsonConverter? converter;
using System.Diagnostics;
using System.Text.Json.Serialization;
using System.Text.Encodings.Web;
-using System.Text.Json.Serialization.Converters;
-using System.Runtime.CompilerServices;
namespace System.Text.Json
{
private ReferenceHandling _referenceHandling = ReferenceHandling.Default;
private JavaScriptEncoder? _encoder = null;
- // Cache common converters.
- private static JsonConverter<object> s_objectConverter = null!;
- private static JsonConverter<JsonElement> s_jsonElementConverter = null!;
-
private int _defaultBufferSize = BufferSizeDefault;
private int _maxDepth;
private bool _allowTrailingCommas;
{
if (_memberAccessorStrategy == null)
{
- if (RuntimeFeature.IsDynamicCodeSupported)
- {
- _memberAccessorStrategy = new ReflectionEmitMemberAccessor();
- }
- else
- {
- _memberAccessorStrategy = new ReflectionMemberAccessor();
- }
+#if NETFRAMEWORK || NETCOREAPP
+ _memberAccessorStrategy = new ReflectionEmitMemberAccessor();
+#else
+ _memberAccessorStrategy = new ReflectionMemberAccessor();
+#endif
}
return _memberAccessorStrategy;
ThrowHelper.ThrowInvalidOperationException_SerializerOptionsImmutable();
}
}
-
- internal static JsonConverter<JsonElement> GetJsonElementConverter()
- {
- if (s_jsonElementConverter == null)
- {
- s_jsonElementConverter = new JsonConverterJsonElement();
- }
-
- return s_jsonElementConverter;
- }
-
- internal static JsonConverter<object> GetObjectConverter()
- {
- if (s_objectConverter == null)
- {
- s_objectConverter = new JsonConverterObject();
- }
-
- return s_objectConverter;
- }
}
}
namespace System.Text.Json.Serialization
{
- // Used for value converters that need to re-enter the serializer since it will
- // support JsonPath and other features that require per-serialization state.
+ // Used for value converters that need to re-enter the serializer since it will support JsonPath
+ // and reference handling.
internal abstract class JsonValueConverter<T> : JsonConverter<T>
{
- internal override sealed ClassType ClassType => ClassType.NewValue;
+ internal sealed override ClassType ClassType => ClassType.NewValue;
public override sealed T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
}
WriteStack state = default;
- state.InitializeRoot(typeof(T), options);
+ state.InitializeRoot(typeof(T), options, supportContinuation: false);
TryWrite(writer, value, options, ref state);
}
}
{
public abstract JsonClassInfo.ConstructorDelegate? CreateConstructor(Type classType);
- public abstract Action<TCollection, object> CreateAddMethodDelegate<TCollection>();
+ public abstract Action<TCollection, object?> CreateAddMethodDelegate<TCollection>();
public abstract Func<IEnumerable<TElement>, TCollection> CreateImmutableEnumerableCreateRangeDelegate<TElement, TCollection>();
// A field is used instead of a property to avoid value semantics.
public ReadStackFrame Current;
- // Support the read-ahead feature.
- public JsonReaderState InitialReaderState;
- public long InitialReaderBytesConsumed;
-
public bool IsContinuation => _continuationCount != 0;
public bool IsLastContinuation => _continuationCount == _count;
if ((Current.JsonClassInfo.ClassType & (ClassType.Object | ClassType.Value | ClassType.NewValue)) != 0)
{
// Although ClassType.Value doesn't push, a custom custom converter may re-enter serialization.
- jsonClassInfo = Current.JsonPropertyInfo.RuntimeClassInfo;
+ jsonClassInfo = Current.JsonPropertyInfo!.RuntimeClassInfo;
}
else
{
{
// No need for a continuation since there is only one stack frame.
_continuationCount = 1;
- _count = 1;
}
else
{
return sb.ToString();
- void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame)
+ static void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame)
{
// Append the property name.
string? propertyName = GetPropertyName(frame);
AppendPropertyName(sb, propertyName);
- // For metadata properties, include the name.
- propertyName = JsonSerializer.GetMetadataPropertyName(in frame);
- AppendPropertyName(sb, propertyName);
-
- if (frame.JsonClassInfo != null)
+ if (frame.JsonClassInfo != null && frame.IsProcessingEnumerable())
{
- if (frame.IsProcessingDictionary())
+ IEnumerable? enumerable = (IEnumerable?)frame.ReturnValue;
+ if (enumerable == null)
{
- // For dictionaries add the key.
- AppendPropertyName(sb, frame.KeyName);
+ return;
}
- else if (frame.IsProcessingEnumerable())
+
+ // Once all elements are read, the exception is not within the array.
+ if (frame.ObjectState < StackFrameObjectState.ReadElements)
{
- IEnumerable enumerable = (IEnumerable)frame.ReturnValue!;
- if (enumerable != null)
- {
- // Once all elements are read, the exception is not within the array.
- if (frame.ObjectState < StackFrameObjectState.ReadElements)
- {
- sb.Append(@"[");
- sb.Append(GetCount(enumerable));
- sb.Append(@"]");
- }
- }
+ sb.Append(@"[");
+ sb.Append(GetCount(enumerable));
+ sb.Append(@"]");
}
}
}
return count;
}
- void AppendPropertyName(StringBuilder sb, string? propertyName)
+ static void AppendPropertyName(StringBuilder sb, string? propertyName)
{
if (propertyName != null)
{
}
}
- string? GetPropertyName(in ReadStackFrame frame)
+ static string? GetPropertyName(in ReadStackFrame frame)
{
string? propertyName = null;
utf8PropertyName = frame.JsonPropertyInfo?.JsonPropertyName;
if (utf8PropertyName == null)
{
- // Attempt to get the JSON property name from the property name specified in re-entry.
+ // Attempt to get the JSON property name set manually for dictionary
+ // keys and serializer re-entry cases where a property is specified.
propertyName = frame.JsonPropertyNameAsString;
}
}
internal struct ReadStackFrame
{
// Current property values.
- public JsonPropertyInfo JsonPropertyInfo;
+ public JsonPropertyInfo? JsonPropertyInfo;
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 re-entry cases.
-
- // Support Dictionary keys.
- public string? KeyName;
+ public string? JsonPropertyNameAsString; // This is used for dictionary keys and re-entry cases that specify a property name.
// Validation state.
public int OriginalDepth;
- public int OriginalPropertyDepth;
- public long OriginalPropertyBytesConsumed;
public JsonTokenType OriginalTokenType;
- public JsonTokenType OriginalPropertyTokenType;
// Current object (POCO or IEnumerable).
public object? ReturnValue; // The current return value used for re-entry.
public StackFrameObjectState ObjectState; // State tracking the current object.
// Preserve reference.
- public MetadataPropertyName MetadataPropertyName;
public string? MetadataId;
// For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
JsonPropertyNameAsString = null;
PropertyState = StackFramePropertyState.None;
MetadataId = null;
- MetadataPropertyName = MetadataPropertyName.NoMetadata;
// No need to clear these since they are overwritten each time:
// UseExtensionProperty
- // OriginalPropertyDepth
- // OriginalPropertyBytesConsumed
- // OriginalPropertyTokenType
}
public void EndElement()
{
- OriginalPropertyDepth = 0;
- OriginalPropertyBytesConsumed = 0;
- OriginalPropertyTokenType = JsonTokenType.None;
- KeyName = null;
+ JsonPropertyNameAsString = null;
PropertyState = StackFramePropertyState.None;
}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-#if BUILDING_INBOX_LIBRARY
-using System.Collections;
+#if NETFRAMEWORK || NETCOREAPP
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
-namespace System.Text.Json
+namespace System.Text.Json.Serialization
{
internal sealed class ReflectionEmitMemberAccessor : MemberAccessor
{
return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate));
}
- public override Action<TCollection, object> CreateAddMethodDelegate<TCollection>()
+ public override Action<TCollection, object?> CreateAddMethodDelegate<TCollection>()
{
Type collectionType = typeof(TCollection);
Type elementType = typeof(object);
- // We verified this won't be null when a created the converter that calls this method.
+ // We verified this won't be null when we created the converter that calls this method.
MethodInfo realMethod = (collectionType.GetMethod("Push") ?? collectionType.GetMethod("Enqueue"))!;
var dynamicMethod = new DynamicMethod(
generator.Emit(OpCodes.Callvirt, realMethod);
generator.Emit(OpCodes.Ret);
- return (Action<TCollection, object>)dynamicMethod.CreateDelegate(typeof(Action<TCollection, object>));
+ return (Action<TCollection, object?>)dynamicMethod.CreateDelegate(typeof(Action<TCollection, object?>));
}
public override Func<IEnumerable<TElement>, TCollection> CreateImmutableEnumerableCreateRangeDelegate<TElement, TCollection>()
// 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.Diagnostics;
using System.Reflection;
-namespace System.Text.Json
+namespace System.Text.Json.Serialization
{
internal sealed class ReflectionMemberAccessor : MemberAccessor
{
return () => Activator.CreateInstance(type);
}
- public override Action<TCollection, object> CreateAddMethodDelegate<TCollection>()
+ public override Action<TCollection, object?> CreateAddMethodDelegate<TCollection>()
{
Type collectionType = typeof(TCollection);
Type elementType = typeof(object);
// We verified this won't be null when we created the converter for the collection type.
MethodInfo addMethod = (collectionType.GetMethod("Push") ?? collectionType.GetMethod("Enqueue"))!;
- return delegate (TCollection collection, object element)
+ return delegate (TCollection collection, object? element)
{
addMethod.Invoke(collection, new object[] { element! });
};
namespace System.Text.Json
{
+ /// <summary>
+ /// The current state of an object or collection that supports continuation.
+ /// The values are typically compared with the less-than operator so the ordering is important.
+ /// </summary>
internal enum StackFrameObjectState : byte
{
None = 0,
MetadataRefPropertyEndObject, // Read EndObject for $ref.
MetadataValuesPropertyName, // Read $values property name.
MetadataValuesPropertyStartArray, // Read StartArray for $values.
- MetataPropertyValue, // Whether all metadata properties has been read.
+ MetadataPropertyValue, // Whether all metadata properties has been read.
CreatedObject,
ReadElements,
namespace System.Text.Json
{
+ /// <summary>
+ /// The current state of a property that supports continuation.
+ /// The values are typically compared with the less-than operator so the ordering is important.
+ /// </summary>
internal enum StackFramePropertyState : byte
{
None = 0,
- ReadName,
- Name,
- ReadValue,
- ReadValueIsEnd,
- TryRead,
+ ReadName, // Read the name of the property.
+ Name, // Verify or process the name.
+ ReadValue, // Read the value of the property.
+ ReadValueIsEnd, // Determine if we are done reading.
+ TryRead, // Perform the actual call to the converter's TryRead().
}
}
/// <summary>
/// Initializes the state for the first type being serialized.
/// </summary>
- public void InitializeRoot(Type type, JsonSerializerOptions options)
+ public void InitializeRoot(Type type, JsonSerializerOptions options, bool supportContinuation)
{
JsonClassInfo jsonClassInfo = options.GetOrAddClass(type);
Debug.Assert(jsonClassInfo.ClassType != ClassType.Invalid);
{
ReferenceResolver = new DefaultReferenceResolver(writing: true);
}
- }
-
- public MetadataPropertyName GetResolvedReferenceHandling(JsonConverter converter, object value, out string? referenceId)
- {
- if (!converter.CanHaveMetadata)
- {
- referenceId = default;
- return MetadataPropertyName.NoMetadata;
- }
-
- if (ReferenceResolver.TryGetOrAddReferenceOnSerialize(value, out referenceId))
- {
- return MetadataPropertyName.Ref;
- }
- return MetadataPropertyName.Id;
+ SupportContinuation = supportContinuation;
}
public void Push()
/// For objects, it is either the policy property for the class or the current property.
/// For collections, it is either the policy property for the class or the policy property for the current element.
/// </remarks>
- public JsonPropertyInfo DeclaredJsonPropertyInfo;
+ public JsonPropertyInfo? DeclaredJsonPropertyInfo;
/// <summary>
- /// Used when processing dictionaries.
+ /// Used when processing extension data dictionaries.
/// </summary>
public bool IgnoreDictionaryKeyPolicy;
public JsonClassInfo JsonClassInfo;
/// <summary>
- /// The key name for a dictionary value.
- /// </summary>
- public string KeyName;
-
- /// <summary>
/// Validation state for a class.
/// </summary>
public int OriginalDepth;
- public int OriginalPropertyDepth;
// Class-level state for collections.
public bool ProcessedStartToken;
/// </remarks>
public JsonPropertyInfo? PolymorphicJsonPropertyInfo;
- public void EndElement()
+ public void EndDictionaryElement()
{
- OriginalPropertyDepth = 0;
PropertyState = StackFramePropertyState.None;
}
{
DeclaredJsonPropertyInfo = null!;
JsonPropertyNameAsString = null;
- KeyName = null!;
- OriginalPropertyDepth = 0;
PolymorphicJsonPropertyInfo = null;
PropertyState = StackFramePropertyState.None;
}
/// Return the property that contains the correct polymorphic properties including
/// the ClassType and ConverterBase.
/// </summary>
- /// <returns></returns>
public JsonPropertyInfo GetPolymorphicJsonPropertyInfo()
{
- return PolymorphicJsonPropertyInfo != null ? PolymorphicJsonPropertyInfo : DeclaredJsonPropertyInfo;
+ return PolymorphicJsonPropertyInfo ?? DeclaredJsonPropertyInfo!;
}
/// <summary>
public JsonConverter InitializeReEntry(Type type, JsonSerializerOptions options, string? propertyName = null)
{
JsonClassInfo newClassInfo = options.GetOrAddClass(type);
- if (newClassInfo.ClassType == ClassType.Invalid)
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(type);
- }
// todo: check if type==newtype and skip below?
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static NotSupportedException GetNotSupportedException_SerializationNotSupportedCollection(Type propertyType, Type? parentType, MemberInfo? memberInfo)
+ public static NotSupportedException GetNotSupportedException_SerializationNotSupported(Type propertyType, Type? parentType, MemberInfo? memberInfo)
{
if (parentType != null && parentType != typeof(object) && memberInfo != null)
{
- return new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollection, propertyType, $"{parentType}.{memberInfo.Name}"));
+ return new NotSupportedException(SR.Format(SR.SerializationNotSupported, propertyType, $"{parentType}.{memberInfo.Name}"));
}
- return new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollectionType, propertyType));
+ return new NotSupportedException(SR.Format(SR.SerializationNotSupportedType, propertyType));
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static NotSupportedException ThrowNotSupportedException_SerializationNotSupportedCollection(Type propertyType, Type? parentType = null, MemberInfo? memberInfo = null)
+ public static NotSupportedException ThrowNotSupportedException_SerializationNotSupported(Type propertyType, Type? parentType = null, MemberInfo? memberInfo = null)
{
- throw GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo);
+ throw GetNotSupportedException_SerializationNotSupported(propertyType, parentType, memberInfo);
}
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidOperationException_SerializerCycleDetected(int maxDepth)
{
throw new JsonException(SR.Format(SR.SerializerCycleDetected, maxDepth));
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType, string path, Exception? innerException = null)
- {
- string message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType) + $" Path: {path}.";
- throw new JsonException(message, path, null, null, innerException);
- }
-
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonException_DepthTooLarge(int currentDepth, JsonSerializerOptions options)
- {
- throw new JsonException(SR.Format(SR.DepthTooLarge, currentDepth, options.EffectiveMaxDepth));
- }
-
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowJsonException_SerializationConverterRead(JsonConverter? converter)
{
var ex = new JsonException(SR.Format(SR.SerializationConverterRead, converter));
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties()
+ public static void ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(ReadOnlySpan<byte> propertyName, ref ReadStack state)
{
+ state.Current.JsonPropertyName = propertyName.ToArray();
ThrowJsonException(SR.MetadataReferenceCannotContainOtherProperties);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties_Dictionary(ref ReadStackFrame current)
- {
- current.KeyName = null;
- ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties();
- }
-
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonException_MetadataIdIsNotFirstProperty()
+ public static void ThrowJsonException_MetadataIdIsNotFirstProperty(ReadOnlySpan<byte> propertyName, ref ReadStack state)
{
+ state.Current.JsonPropertyName = propertyName.ToArray();
ThrowJsonException(SR.MetadataIdIsNotFirstProperty);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonException_MetadataIdIsNotFirstProperty_Dictionary(ref ReadStackFrame current)
- {
- current.KeyName = null;
- ThrowJsonException_MetadataIdIsNotFirstProperty();
- }
-
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowJsonException_MetadataMissingIdBeforeValues()
{
ThrowJsonException(SR.MetadataPreservedArrayPropertyNotFound);
// Set PropertyInfo or KeyName to write down the conflicting property name in JsonException.Path
if (state.Current.IsProcessingDictionary())
{
- state.Current.KeyName = reader.GetString();
+ state.Current.JsonPropertyNameAsString = reader.GetString();
}
else
{
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonException_MetadataDuplicateIdFound(string id)
+ public static void ThrowJsonException_MetadataDuplicateIdFound(string id, ref ReadStack state)
{
+ // Set so JsonPath throws exception with $id in it.
+ state.Current.JsonPropertyName = JsonSerializer.s_metadataId.EncodedUtf8Bytes.ToArray();
+
ThrowJsonException(SR.Format(SR.MetadataDuplicateIdFound, id));
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonException_MetadataPreservedArrayInvalidProperty(Type propertyType, in Utf8JsonReader reader, ref ReadStack state)
+ public static void ThrowJsonException_MetadataPreservedArrayInvalidProperty(Type propertyType, in Utf8JsonReader reader)
{
string propertyName = reader.GetString()!;
{
ThrowJsonException(SR.Format(SR.MetadataCannotParsePreservedObjectToImmutable, propertyType));
}
+
+ [DoesNotReturn]
+ internal static void ThrowUnexpectedMetadataException(
+ ReadOnlySpan<byte> propertyName,
+ ref Utf8JsonReader reader,
+ ref ReadStack state)
+ {
+ MetadataPropertyName name = JsonSerializer.GetMetadataPropertyName(propertyName);
+ if (name == MetadataPropertyName.Id)
+ {
+ ThrowJsonException_MetadataIdIsNotFirstProperty(propertyName, ref state);
+ }
+ else if (name == MetadataPropertyName.Ref)
+ {
+ ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(propertyName, ref state);
+ }
+ else
+ {
+ ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader);
+ }
+ }
}
}
class Dealer
{
- private string _Networks;
+ private string _networks;
[JsonIgnore]
public string Networks
{
- get => _Networks;
- set => _Networks = value ?? string.Empty;
+ get => _networks;
+ set => _networks = value ?? string.Empty;
}
public IEnumerable<string> NetworkCodeList
{
public override bool CanConvert(Type typeToConvert)
{
- return true;
+ // To verify the nullable converter, don't convert Nullable.
+ return Nullable.GetUnderlyingType(typeToConvert) == null;
}
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<int>("0", options));
Assert.Contains(typeof(int).ToString(), ex.Message);
+
+ // This will invoke the Nullable converter which should detect a null converter.
+ ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<int?>("0", options));
+ Assert.Contains(typeof(int).ToString(), ex.Message);
}
private class Level1
Assert.Equal(Json, json);
}
- public class DictionaryThatHasIncomatibleEnumerator : IDictionary
+ public class DictionaryThatHasIncompatibleEnumerator : IDictionary
{
Hashtable _inner = new Hashtable();
{
const string Json = @"{""One"":1,""Two"":2}";
- DictionaryThatHasIncomatibleEnumerator dictionary;
- dictionary = JsonSerializer.Deserialize<DictionaryThatHasIncomatibleEnumerator>(Json);
+ DictionaryThatHasIncompatibleEnumerator dictionary;
+ dictionary = JsonSerializer.Deserialize<DictionaryThatHasIncompatibleEnumerator>(Json);
Assert.Equal(1, ((JsonElement)dictionary["One"]).GetInt32());
Assert.Equal(2, ((JsonElement)dictionary["Two"]).GetInt32());
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(dictionary));
{
const string Json = @"{""One"":{""Id"":1},""Two"":{""Id"":2}}";
- DictionaryThatHasIncomatibleEnumerator dictionary;
- dictionary = JsonSerializer.Deserialize<DictionaryThatHasIncomatibleEnumerator>(Json);
+ DictionaryThatHasIncompatibleEnumerator dictionary;
+ dictionary = JsonSerializer.Deserialize<DictionaryThatHasIncompatibleEnumerator>(Json);
Assert.Equal(1, ((JsonElement)dictionary["One"]).GetProperty("Id").GetInt32());
Assert.Equal(2, ((JsonElement)dictionary["Two"]).GetProperty("Id").GetInt32());
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(dictionary));
}
+
+ [Fact]
+ public static void VerifyIDictionaryWithNonStringKey()
+ {
+ IDictionary dictionary = new Hashtable();
+ dictionary.Add(1, "value");
+ Assert.Throws<JsonException>(() => JsonSerializer.Serialize(dictionary));
+ }
+
public class ClassWithoutParameterlessCtor
{
public ClassWithoutParameterlessCtor(int num) { }
public Dictionary<string, ChildClass> MyDictionary { get; set; }
public ChildClass[] Children { get; set; }
}
+
+ private class ClassWithInvalidArray
+ {
+ public int[,] UnsupportedArray { get; set; }
+ }
+
+ private class ClassWithInvalidDictionary
+ {
+ public Dictionary<string, int[,]> UnsupportedDictionary { get; set; }
+ }
+
+ [Fact]
+ public static void ClassWithUnsupportedCollectionTypes()
+ {
+ Exception ex;
+
+ ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithInvalidArray>(@"{""UnsupportedArray"":[]}"));
+
+ // The exception should contain the parent type and the property name.
+ Assert.Contains("ClassWithInvalidArray.UnsupportedArray", ex.ToString());
+
+ ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithInvalidDictionary>(@"{""UnsupportedDictionary"":{}}"));
+ Assert.Contains("System.Int32[,]", ex.ToString());
+
+ // The exception for element types do not contain the parent type and the property name
+ // since the verification occurs later and is no longer bound to the parent type.
+ Assert.DoesNotContain("ClassWithInvalidDictionary.UnsupportedDictionary", ex.ToString());
+ }
}
}
// 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.Diagnostics;
using System.Linq;
using Xunit;
public Dictionary<string, object> ActualDictionary { get; set; }
}
+
+ [Fact]
+ public static void CustomObjectConverterInExtensionProperty()
+ {
+ const string Json = "{\"hello\": \"world\"}";
+
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new JsonObjectConverter());
+
+ ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(Json, options);
+ object overflowProp = obj.MyOverflow["hello"];
+ Assert.IsType<string>(overflowProp);
+ Assert.Equal("world!!!", ((string)overflowProp));
+
+ string newJson = JsonSerializer.Serialize(obj, options);
+ Assert.Equal("{\"hello\":\"world!!!\"}", newJson);
+ }
+
+ private class JsonObjectConverter : JsonConverter<object>
+ {
+ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return reader.GetString() + "!!!";
+ }
+
+ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
+ {
+ // Since we are converter for object, the string converter will be called instead of this.
+ throw new InvalidOperationException();
+ }
+ }
+
+ [Fact]
+ public static void CustomJsonElementConverterInExtensionProperty()
+ {
+ const string Json = "{\"hello\": \"world\"}";
+
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new JsonElementConverter());
+
+ ClassWithExtensionPropertyAsJsonElement obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(Json, options);
+ JsonElement overflowProp = obj.MyOverflow["hello"];
+ Assert.Equal(JsonValueKind.Undefined, overflowProp.ValueKind);
+
+ string newJson = JsonSerializer.Serialize(obj, options);
+ Assert.Equal("{\"hello\":{\"Hi\":\"There\"}}", newJson);
+ }
+
+ private class JsonElementConverter : JsonConverter<JsonElement>
+ {
+ public override JsonElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ // Just return an empty JsonElement.
+ reader.Skip();
+ return new JsonElement();
+ }
+
+ public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
+ {
+ // Write a string we can test against easily.
+ writer.WriteStartObject();
+ writer.WriteString("Hi", "There");
+ writer.WriteEndObject();
+ }
+ }
}
}
}
#region Root Object
+ [Fact]
+ public static void ObjectWithoutMetadata()
+ {
+ string json = "{}";
+ Employee employee = JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve);
+ Assert.NotNull(employee);
+ }
+
[Fact] //Employee list as a property and then use reference to itself on nested Employee.
public static void ObjectReferenceLoop()
{
#endregion
#region Root Dictionary
+ [Fact]
+ public static void DictionaryWithoutMetadata()
+ {
+ string json = "{}";
+ Dictionary<string, string> dictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(json, s_deserializerOptionsPreserve);
+ Assert.NotNull(dictionary);
+ }
+
[Fact] //Employee list as a property and then use reference to itself on nested Employee.
public static void DictionaryReferenceLoop()
{
]";
JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<EmployeeStruct>>(json, s_deserializerOptionsPreserve));
- Assert.Equal("$[0].$id", ex.Path);
+ Assert.Equal("$[1].$ref", ex.Path);
Assert.Contains($"'{typeof(EmployeeStruct)}'", ex.Message);
}
#endregion
}";
ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve));
- Assert.Equal("$.Manager.$ref", ex.Path);
+ Assert.Equal("$.Manager.Name", ex.Path);
//Metadata property before $ref
json = @"{
}";
ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve));
- Assert.Equal("$.Manager.$ref", ex.Path);
+ Assert.Equal("$.Manager.$id", ex.Path);
}
[Fact]
Assert.Contains("'1'", ex.Message);
Assert.Equal("$[0].$ref", ex.Path);
}
+
+ [Theory]
+ [MemberData(nameof(ReadSuccessCases))]
+ public static void ReadTestClassesWithExtensionOption(Type classType, byte[] data)
+ {
+ var options = new JsonSerializerOptions { ReferenceHandling = ReferenceHandling.Preserve };
+ object obj = JsonSerializer.Deserialize(data, classType, options);
+ Assert.IsAssignableFrom<ITestClass>(obj);
+ ((ITestClass)obj).Verify();
+ }
+
+ public static IEnumerable<object[]> ReadSuccessCases
+ {
+ get
+ {
+ return TestData.ReadSuccessCases;
+ }
+ }
+
#endregion
#region Preserved objects ($id)
JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<int>>(json, s_deserializerOptionsPreserve));
- Assert.Equal("$.$values", ex.Path);
+ Assert.Equal("$.$values", ex.Path);
}
[Fact]
JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<Employee>>(json, s_deserializerOptionsPreserve));
- Assert.Equal("$", ex.Path);
+ Assert.Equal("$.LeadingProperty", ex.Path);
Assert.Contains(typeof(List<Employee>).ToString(), ex.Message);
json = @"{
ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<Employee>>(json, s_deserializerOptionsPreserve));
- Assert.Equal("$", ex.Path);
+ Assert.Equal("$.TrailingProperty", ex.Path);
Assert.Contains(typeof(List<Employee>).ToString(), ex.Message);
Assert.Contains("TrailingProperty", ex.Message);
}
[InlineData(2, false, false)]
[InlineData(4, false, false)]
[InlineData(8, false, false)]
- [InlineData(16, false, false)]
+ [InlineData(16, false, false)] // This results a reader\writer depth of 324 which currently works on all test platforms.
public static async Task DeepNestedJsonFileTest(int depthFactor, bool ignoreNull, bool writeIndented)
{
- int length = 10 * depthFactor;
+ const int ListLength = 10;
+
+ int length = ListLength * depthFactor;
List<Order>[] orders = new List<Order>[length];
orders[0] = PopulateLargeObject(1);
for (int i = 1; i < length; i++ )
JsonSerializerOptions options = new JsonSerializerOptions()
{
- MaxDepth = depthFactor * 64,
+ MaxDepth = (ListLength * depthFactor * 2) + 4, // Order-to-RelatedOrder has a depth of 2.
IgnoreNullValues = ignoreNull,
WriteIndented = writeIndented
};
[Theory]
[InlineData(1)]
- [InlineData(16)]
- public static async Task DeepNestedJsonFileCircularDependencyTest(int depthFactor)
+ [InlineData(4)]
+ public static async Task NestedJsonFileCircularDependencyTest(int depthFactor)
{
- int length = 10 * depthFactor;
+ const int ListLength = 2;
+
+ int length = ListLength * depthFactor;
List<Order>[] orders = new List<Order>[length];
orders[0] = PopulateLargeObject(1000);
for (int i = 1; i < length; i++)
orders[i] = PopulateLargeObject(1);
orders[i - 1][0].RelatedOrder = orders[i];
}
- orders[length - 1][0].RelatedOrder = orders[0];
JsonSerializerOptions options = new JsonSerializerOptions()
{
- MaxDepth = depthFactor * 64,
IgnoreNullValues = true
};
+ // Ensure no exception for default settings (MaxDepth=64) and no cycle.
+ JsonSerializer.Serialize(orders[0], options);
+
+ // Create a cycle.
+ orders[length - 1][0].RelatedOrder = orders[0];
+
Assert.Throws<JsonException> (() => JsonSerializer.Serialize(orders[0], options));
using (var memoryStream = new MemoryStream())
ConcurrentQueue<string> qc = JsonSerializer.Deserialize<ConcurrentQueue<string>>(@"[""1""]");
Assert.Equal(1, qc.Count);
- qc.TryPeek(out string val);
+ bool found = qc.TryPeek(out string val);
+ Assert.True(found);
Assert.Equal("1", val);
ConcurrentStack<string> qs = JsonSerializer.Deserialize<ConcurrentStack<string>>(@"[""1""]");
Assert.Equal(1, qs.Count);
- qs.TryPeek(out val);
+ found = qs.TryPeek(out val);
+ Assert.True(found);
Assert.Equal("1", val);
}
[Fact]
public static void Read_SpecializedCollection_Throws()
{
- //// Add method for this collection only accepts strings, even though it only implements IList which usually
- //// indicates that the element type is typeof(object).
- //Assert.Throws<InvalidCastException>(() => JsonSerializer.Deserialize<StringCollection>(@"[""1"", ""2""]"));
+ // Add method for this collection only accepts strings, even though it only implements IList which usually
+ // indicates that the element type is typeof(object).
+ Assert.Throws<InvalidCastException>(() => JsonSerializer.Deserialize<StringCollection>(@"[""1"", ""2""]"));
- //// Not supported. Not IList, and we don't detect the add method for this collection.
- //Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<StringDictionary>(@"[{""Key"": ""key"",""Value"":""value""}]"));
+ // Not supported. Not IList, and we don't detect the add method for this collection.
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<StringDictionary>(@"[{""Key"": ""key"",""Value"":""value""}]"));
- //// Int key is not allowed.
- //Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<HybridDictionary>(@"{1:""value""}"));
+ // Int key is not allowed.
+ Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<HybridDictionary>(@"{1:""value""}"));
// Runtime type in this case is IOrderedDictionary (we don't replace with concrete type), which we can't instantiate.
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<IOrderedDictionary>(@"{""first"":""John"",""second"":""Jane"",""third"":""Jet""}"));