protected static JsonConverter<TElement> GetElementConverter(ref WriteStack state)
{
- JsonConverter<TElement> converter = (JsonConverter<TElement>)state.Current.DeclaredJsonPropertyInfo!.ConverterBase;
+ JsonConverter<TElement> converter = (JsonConverter<TElement>)state.Current.JsonPropertyInfo!.ConverterBase;
Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
return converter;
writer.WriteStartArray();
}
- state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
+ state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
}
success = OnWriteResume(writer, value, options, ref state);
}
}
- state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
+ state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
}
bool success = OnWriteResume(writer, dictionary, options, ref state);
}
TElement element = _optionValueGetter(value);
- state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
+ state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
return _elementConverter.TryWrite(writer, element, options, ref state);
}
TElement element = _optionValueGetter(ref value);
- state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
+ state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
return _elementConverter.TryWrite(writer, element, options, ref state);
}
if (jsonPropertyInfo.ShouldSerialize)
{
// Remember the current property for JsonPath support if an exception is thrown.
- state.Current.DeclaredJsonPropertyInfo = jsonPropertyInfo;
+ state.Current.JsonPropertyInfo = jsonPropertyInfo;
state.Current.NumberHandling = jsonPropertyInfo.NumberHandling;
bool success = jsonPropertyInfo.GetMemberAndWriteJson(obj, ref state, writer);
if (dataExtensionProperty?.ShouldSerialize == true)
{
// Remember the current property for JsonPath support if an exception is thrown.
- state.Current.DeclaredJsonPropertyInfo = dataExtensionProperty;
+ state.Current.JsonPropertyInfo = dataExtensionProperty;
state.Current.NumberHandling = dataExtensionProperty.NumberHandling;
bool success = dataExtensionProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer);
Debug.Assert(jsonPropertyInfo != null);
if (jsonPropertyInfo.ShouldSerialize)
{
- state.Current.DeclaredJsonPropertyInfo = jsonPropertyInfo;
+ state.Current.JsonPropertyInfo = jsonPropertyInfo;
state.Current.NumberHandling = jsonPropertyInfo.NumberHandling;
if (!jsonPropertyInfo.GetMemberAndWriteJson(obj!, ref state, writer))
if (dataExtensionProperty?.ShouldSerialize == true)
{
// Remember the current property for JsonPath support if an exception is thrown.
- state.Current.DeclaredJsonPropertyInfo = dataExtensionProperty;
+ state.Current.JsonPropertyInfo = dataExtensionProperty;
state.Current.NumberHandling = dataExtensionProperty.NumberHandling;
if (!dataExtensionProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer))
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics;
-
namespace System.Text.Json.Serialization.Converters
{
internal sealed class NullableConverter<T> : JsonConverter<T?> where T : struct
{
+ internal override ConverterStrategy ConverterStrategy { get; }
+ internal override Type? ElementType => typeof(T);
+ public override bool HandleNull => true;
+
// 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 readonly JsonConverter<T> _converter;
+ private readonly JsonConverter<T> _elementConverter;
+
+ public NullableConverter(JsonConverter<T> elementConverter)
+ {
+ _elementConverter = elementConverter;
+ ConverterStrategy = elementConverter.ConverterStrategy;
+ IsInternalConverterForNumberType = elementConverter.IsInternalConverterForNumberType;
+ // temporary workaround for JsonConverter base constructor needing to access
+ // ConverterStrategy when calculating `CanUseDirectReadOrWrite`.
+ CanUseDirectReadOrWrite = elementConverter.ConverterStrategy == ConverterStrategy.Value;
+ }
+
+ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out T? value)
+ {
+ if (!state.IsContinuation && reader.TokenType == JsonTokenType.Null)
+ {
+ value = null;
+ return true;
+ }
- public NullableConverter(JsonConverter<T> converter)
+ state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
+ if (_elementConverter.TryRead(ref reader, typeof(T), options, ref state, out T element))
+ {
+ value = element;
+ return true;
+ }
+
+ value = null;
+ return false;
+ }
+
+ internal override bool OnTryWrite(Utf8JsonWriter writer, T? value, JsonSerializerOptions options, ref WriteStack state)
{
- _converter = converter;
- IsInternalConverterForNumberType = converter.IsInternalConverterForNumberType;
+ if (value is null)
+ {
+ writer.WriteNullValue();
+ return true;
+ }
+
+ state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
+ return _elementConverter.TryWrite(writer, value.Value, options, ref state);
}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- // We do not check _converter.HandleNull, as the underlying struct cannot be null.
- // A custom converter for some type T? can handle null.
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
- T value = _converter.Read(ref reader, typeof(T), options);
+ T value = _elementConverter.Read(ref reader, typeof(T), options);
return value;
}
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
- if (!value.HasValue)
+ if (value is null)
{
- // We do not check _converter.HandleNull, as the underlying struct cannot be null.
- // A custom converter for some type T? can handle null.
writer.WriteNullValue();
}
else
{
- _converter.Write(writer, value.Value, options);
+ _elementConverter.Write(writer, value.Value, options);
}
}
internal override T? ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling numberHandling, JsonSerializerOptions options)
{
- // We do not check _converter.HandleNull, as the underlying struct cannot be null.
- // A custom converter for some type T? can handle null.
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
- T value = _converter.ReadNumberWithCustomHandling(ref reader, numberHandling, options);
+ T value = _elementConverter.ReadNumberWithCustomHandling(ref reader, numberHandling, options);
return value;
}
internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, T? value, JsonNumberHandling handling)
{
- if (!value.HasValue)
+ if (value is null)
{
- // We do not check _converter.HandleNull, as the underlying struct cannot be null.
- // A custom converter for some type T? can handle null.
writer.WriteNullValue();
}
else
{
- _converter.WriteNumberWithCustomHandling(writer, value.Value, handling);
+ _elementConverter.WriteNumberWithCustomHandling(writer, value.Value, handling);
}
}
}
JsonTypeInfo originalJsonTypeInfo = state.Current.JsonTypeInfo;
#endif
state.Push();
- Debug.Assert(TypeToConvert.IsAssignableFrom(state.Current.JsonTypeInfo.Type));
+ Debug.Assert(TypeToConvert == state.Current.JsonTypeInfo.Type);
#if !DEBUG
// For performance, only perform validation on internal converters on debug builds.
JsonTypeInfo originalJsonTypeInfo = state.Current.JsonTypeInfo;
#endif
state.Push();
- Debug.Assert(TypeToConvert.IsAssignableFrom(state.Current.JsonTypeInfo.Type));
+ Debug.Assert(TypeToConvert == state.Current.JsonTypeInfo.Type);
if (!isContinuation)
{
// Extension data properties change how dictionary key naming policies are applied.
state.Current.IsWritingExtensionDataProperty = true;
- state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
+ state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
success = dictionaryConverter.OnWriteResume(writer, value, options, ref state);
if (success)
/// <remarks>This API is for use by the output of the System.Text.Json source generator and should not be called directly.</remarks>
public static JsonTypeInfo<T> CreateValueInfo<T>(JsonSerializerOptions options, JsonConverter converter)
{
- JsonTypeInfo<T> info = new JsonTypeInfoInternal<T>(options);
+ JsonTypeInfo<T> info = new JsonTypeInfoInternal<T>(converter, options);
info.PropertyInfoForTypeInfo = CreateJsonPropertyInfoForClassInfo(typeof(T), info, converter, options);
converter.ConfigureJsonTypeInfo(info, options);
return info;
/// <summary>
/// Creates serialization metadata for a type using a simple converter.
/// </summary>
- public JsonTypeInfoInternal(JsonSerializerOptions options)
+ public JsonTypeInfoInternal(JsonConverter converter, JsonSerializerOptions options)
: base(typeof(T), options)
{
+ ElementType = converter.ElementType;
}
/// <summary>
#pragma warning restore CS8714
PropInitFunc = objectInfo.PropertyMetadataInitializer;
+ ElementType = converter.ElementType;
SerializeHandler = objectInfo.SerializeHandler;
PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options);
NumberHandling = objectInfo.NumberHandling;
internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation)
{
Current.JsonTypeInfo = jsonTypeInfo;
- Current.DeclaredJsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
- Current.NumberHandling = Current.DeclaredJsonPropertyInfo.NumberHandling;
+ Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
+ Current.NumberHandling = Current.JsonPropertyInfo.NumberHandling;
JsonSerializerOptions options = jsonTypeInfo.Options;
if (options.ReferenceHandlingStrategy != ReferenceHandlingStrategy.None)
_count++;
Current.JsonTypeInfo = jsonTypeInfo;
- Current.DeclaredJsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
+ Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
// Allow number handling on property to win over handling on type.
- Current.NumberHandling = numberHandling ?? Current.DeclaredJsonPropertyInfo.NumberHandling;
+ Current.NumberHandling = numberHandling ?? Current.JsonPropertyInfo.NumberHandling;
}
}
else
static void AppendStackFrame(StringBuilder sb, ref WriteStackFrame frame)
{
// Append the property name.
- string? propertyName = frame.DeclaredJsonPropertyInfo?.ClrName;
+ string? propertyName = frame.JsonPropertyInfo?.ClrName;
if (propertyName == null)
{
// Attempt to get the JSON property name from the property name specified in re-entry.
/// For objects, it is either the actual (real) JsonPropertyInfo or the <see cref="JsonTypeInfo.PropertyInfoForTypeInfo"/> for the class.
/// For collections, it is the <see cref="JsonTypeInfo.PropertyInfoForTypeInfo"/> for the class and current element.
/// </remarks>
- public JsonPropertyInfo? DeclaredJsonPropertyInfo;
+ public JsonPropertyInfo? JsonPropertyInfo;
/// <summary>
/// Used when processing extension data dictionaries.
public void EndProperty()
{
- DeclaredJsonPropertyInfo = null!;
+ JsonPropertyInfo = null!;
JsonPropertyNameAsString = null;
PolymorphicJsonPropertyInfo = null;
PropertyState = StackFramePropertyState.None;
/// </summary>
public JsonPropertyInfo GetPolymorphicJsonPropertyInfo()
{
- return PolymorphicJsonPropertyInfo ?? DeclaredJsonPropertyInfo!;
+ return PolymorphicJsonPropertyInfo ?? JsonPropertyInfo!;
}
/// <summary>
Debug.Assert(!message.Contains(" Path: "));
// Obtain the type to show in the message.
- Type? propertyType = state.Current.DeclaredJsonPropertyInfo?.PropertyType;
+ Type? propertyType = state.Current.JsonPropertyInfo?.PropertyType;
if (propertyType == null)
{
propertyType = state.Current.JsonTypeInfo.Type;
Assert.Equal(1, asyncEnumerable.TotalDisposedEnumerators);
}
+ [Theory]
+ [MemberData(nameof(GetAsyncEnumerableSources))]
+ public async Task WriteNestedAsyncEnumerable_Nullable<TElement>(IEnumerable<TElement> source, int delayInterval, int bufferSize)
+ {
+ // Primarily tests the ability of NullableConverter to flow async serialization state
+
+ JsonSerializerOptions options = new JsonSerializerOptions
+ {
+ DefaultBufferSize = bufferSize,
+ IncludeFields = true,
+ };
+
+ string expectedJson = await JsonSerializerWrapperForString.SerializeWrapper<(IEnumerable<TElement>, bool)?>((source, false), options);
+
+ using var stream = new Utf8MemoryStream();
+ var asyncEnumerable = new MockedAsyncEnumerable<TElement>(source, delayInterval);
+ await JsonSerializerWrapperForStream.SerializeWrapper<(IAsyncEnumerable<TElement>, bool)?>(stream, (asyncEnumerable, false), options);
+
+ JsonTestHelper.AssertJsonEqual(expectedJson, stream.ToString());
+ Assert.Equal(1, asyncEnumerable.TotalCreatedEnumerators);
+ Assert.Equal(1, asyncEnumerable.TotalDisposedEnumerators);
+ }
+
[Theory, OuterLoop]
[InlineData(5000, 1000, true)]
[InlineData(5000, 1000, false)]