Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ System.Text.Json.Nodes.JsonValue</PackageDescription>
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Node.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.Caching.cs" />
<Compile Include="System\Text\Json\Serialization\PolymorphicSerializationState.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceEqualsWrapper.cs" />
<Compile Include="System\Text\Json\Serialization\ConverterStrategy.cs" />
<Compile Include="System\Text\Json\Serialization\ConverterList.cs" />
Expand Down Expand Up @@ -165,6 +166,7 @@ System.Text.Json.Nodes.JsonValue</PackageDescription>
<Compile Include="System\Text\Json\Serialization\Converters\Node\JsonValueConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\JsonObjectConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\KeyValuePairConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectDefaultConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectWithParameterizedConstructorConverter.cs" />
Expand All @@ -189,7 +191,6 @@ System.Text.Json.Nodes.JsonValue</PackageDescription>
<Compile Include="System\Text\Json\Serialization\Converters\Value\JsonElementConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\NullableConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\NullableConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\ObjectConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\SByteConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\SingleConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\StringConverter.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ namespace System.Text.Json
/// </remarks>
internal enum ConverterStrategy : byte
{
// Default - no class type.
/// <summary>
/// Default value; not used by any converter.
/// </summary>
None = 0x0,
// JsonObjectConverter<> - objects with properties.
/// <summary>
/// Objects with properties.
/// </summary>
Object = 0x1,
// JsonConverter<> - simple values.
/// <summary>
/// Simple values or user-provided custom converters.
/// </summary>
Value = 0x2,
// JsonIEnumerableConverter<> - all enumerable collections except dictionaries.
/// <summary>
/// Enumerable collections except dictionaries.
/// </summary>
Enumerable = 0x8,
// JsonDictionaryConverter<,> - dictionary types.
/// <summary>
/// Dictionary types.
/// </summary>
Dictionary = 0x10,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ internal override bool OnTryRead(
{
state.Current.PropertyState = StackFramePropertyState.ReadValue;

if (!SingleValueReadWithReadAhead(elementConverter.ConverterStrategy, ref reader, ref state))
if (!SingleValueReadWithReadAhead(elementConverter.RequiresReadAhead, ref reader, ref state))
{
value = default;
return false;
Expand Down Expand Up @@ -269,12 +269,8 @@ internal override bool OnTryWrite(
state.Current.ProcessedStartToken = true;
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
MetadataPropertyName metadata = JsonSerializer.WriteReferenceForCollection(this, value, ref state, writer);
if (metadata == MetadataPropertyName.Ref)
{
return true;
}

MetadataPropertyName metadata = JsonSerializer.WriteReferenceForCollection(this, ref state, writer);
Debug.Assert(metadata != MetadataPropertyName.Ref);
state.Current.MetadataPropertyName = metadata;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ internal sealed override bool OnTryRead(
{
state.Current.PropertyState = StackFramePropertyState.ReadValue;

if (!SingleValueReadWithReadAhead(_valueConverter.ConverterStrategy, ref reader, ref state))
if (!SingleValueReadWithReadAhead(_valueConverter.RequiresReadAhead, ref reader, ref state))
{
state.Current.DictionaryKey = key;
value = default;
Expand Down Expand Up @@ -311,10 +311,8 @@ internal sealed override bool OnTryWrite(
writer.WriteStartObject();
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
{
return true;
}
MetadataPropertyName propertyName = JsonSerializer.WriteReferenceForObject(this, ref state, writer);
Debug.Assert(propertyName != MetadataPropertyName.Ref);
}

state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ public FSharpOptionConverter(JsonConverter<TElement> elementConverter)
_optionValueGetter = FSharpCoreReflectionProxy.Instance.CreateFSharpOptionValueGetter<TOption, TElement>();
_optionConstructor = FSharpCoreReflectionProxy.Instance.CreateFSharpOptionSomeConstructor<TOption, TElement>();

// temporary workaround for JsonConverter base constructor needing to access
// ConverterStrategy when calculating `CanUseDirectReadOrWrite`.
// TODO move `CanUseDirectReadOrWrite` from JsonConverter to JsonTypeInfo.
_converterStrategy = _elementConverter.ConverterStrategy;
CanUseDirectReadOrWrite = _converterStrategy == ConverterStrategy.Value;
// Workaround for the base constructor depending on the (still unset) ConverterStrategy
// to derive the CanUseDirectReadOrWrite and RequiresReadAhead values.
_converterStrategy = elementConverter.ConverterStrategy;
CanUseDirectReadOrWrite = elementConverter.CanUseDirectReadOrWrite;
RequiresReadAhead = elementConverter.RequiresReadAhead;
}

internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out TOption? value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ public FSharpValueOptionConverter(JsonConverter<TElement> elementConverter)
_optionValueGetter = FSharpCoreReflectionProxy.Instance.CreateFSharpValueOptionValueGetter<TValueOption, TElement>();
_optionConstructor = FSharpCoreReflectionProxy.Instance.CreateFSharpValueOptionSomeConstructor<TValueOption, TElement>();

// temporary workaround for JsonConverter base constructor needing to access
// ConverterStrategy when calculating `CanUseDirectReadOrWrite`.
// TODO move `CanUseDirectReadOrWrite` from JsonConverter to JsonTypeInfo.
_converterStrategy = _elementConverter.ConverterStrategy;
CanUseDirectReadOrWrite = _converterStrategy == ConverterStrategy.Value;
// Workaround for the base constructor depending on the (still unset) ConverterStrategy
// to derive the CanUseDirectReadOrWrite and RequiresReadAhead values.
_converterStrategy = elementConverter.ConverterStrategy;
CanUseDirectReadOrWrite = elementConverter.CanUseDirectReadOrWrite;
RequiresReadAhead = elementConverter.RequiresReadAhead;
}

internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out TValueOption value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,59 @@ namespace System.Text.Json.Serialization.Converters
{
internal sealed class ObjectConverter : JsonConverter<object?>
{
internal override ConverterStrategy ConverterStrategy => ConverterStrategy.Object;

public ObjectConverter()
{
CanBePolymorphic = true;
// JsonElement/JsonNode parsing does not support async; force read ahead for now.
RequiresReadAhead = true;
}

public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (options.UnknownTypeHandling == JsonUnknownTypeHandling.JsonElement)
{
return JsonElement.ParseValue(ref reader);
}

Debug.Assert(options.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode);
return JsonNodeConverter.Instance.Read(ref reader, typeToConvert, options);
}

public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options)
{
Debug.Assert(value?.GetType() == typeof(object));

writer.WriteStartObject();
writer.WriteEndObject();
}

internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out object? value)
{
if (options.UnknownTypeHandling == JsonUnknownTypeHandling.JsonElement)
{
JsonElement element = JsonElement.ParseValue(ref reader);

// Edge case where we want to lookup for a reference when parsing into typeof(object)
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve &&
JsonSerializer.TryGetReferenceFromJsonElement(ref state, element, out object? referenceValue))
{
value = referenceValue;
}
else
{
value = element;
}

return true;
}

Debug.Assert(options.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode);
value = JsonNodeConverter.Instance.Read(ref reader, typeToConvert, options)!;
// TODO reference lookup for JsonNode deserialization.
return true;
}

internal override object ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,8 @@ internal sealed override bool OnTryWrite(
writer.WriteStartObject();
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
if (JsonSerializer.WriteReferenceForObject(this, obj, ref state, writer) == MetadataPropertyName.Ref)
{
return true;
}
MetadataPropertyName propertyName = JsonSerializer.WriteReferenceForObject(this, ref state, writer);
Debug.Assert(propertyName != MetadataPropertyName.Ref);
}

if (obj is IJsonOnSerializing onSerializing)
Expand Down Expand Up @@ -313,10 +311,8 @@ internal sealed override bool OnTryWrite(
writer.WriteStartObject();
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
if (JsonSerializer.WriteReferenceForObject(this, obj, ref state, writer) == MetadataPropertyName.Ref)
{
return true;
}
MetadataPropertyName propertyName = JsonSerializer.WriteReferenceForObject(this, ref state, writer);
Debug.Assert(propertyName != MetadataPropertyName.Ref);
}

if (obj is IJsonOnSerializing onSerializing)
Expand All @@ -339,9 +335,7 @@ internal sealed override bool OnTryWrite(

if (!jsonPropertyInfo.GetMemberAndWriteJson(obj!, ref state, writer))
{
Debug.Assert(jsonPropertyInfo.ConverterBase.ConverterStrategy != ConverterStrategy.Value ||
jsonPropertyInfo.ConverterBase.TypeToConvert == JsonTypeInfo.ObjectType);

Debug.Assert(jsonPropertyInfo.ConverterBase.ConverterStrategy != ConverterStrategy.Value);
return false;
}

Expand Down Expand Up @@ -443,15 +437,15 @@ protected static bool ReadAheadPropertyValue(ref ReadStack state, ref Utf8JsonRe

if (!state.Current.UseExtensionProperty)
{
if (!SingleValueReadWithReadAhead(jsonPropertyInfo.ConverterBase.ConverterStrategy, ref reader, ref state))
if (!SingleValueReadWithReadAhead(jsonPropertyInfo.ConverterBase.RequiresReadAhead, ref reader, ref state))
{
return false;
}
}
else
{
// The actual converter is JsonElement, so force a read-ahead.
if (!SingleValueReadWithReadAhead(ConverterStrategy.Value, ref reader, ref state))
if (!SingleValueReadWithReadAhead(requiresReadAhead: true, ref reader, ref state))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ private bool HandleConstructorArgumentWithContinuation(
// Returning false below will cause the read-ahead functionality to finish the read.
state.Current.PropertyState = StackFramePropertyState.ReadValue;

if (!SingleValueReadWithReadAhead(jsonParameterInfo.ConverterBase.ConverterStrategy, ref reader, ref state))
if (!SingleValueReadWithReadAhead(jsonParameterInfo.ConverterBase.RequiresReadAhead, ref reader, ref state))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ internal sealed class NullableConverter<T> : JsonConverter<T?> where T : struct
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;

// Workaround for the base constructor depending on the (still unset) ConverterStrategy
// to derive the CanUseDirectReadOrWrite and RequiresReadAhead values.
ConverterStrategy = elementConverter.ConverterStrategy;
CanUseDirectReadOrWrite = elementConverter.CanUseDirectReadOrWrite;
RequiresReadAhead = elementConverter.RequiresReadAhead;
}

internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out T? value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public abstract partial class JsonConverter
// 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 SingleValueReadWithReadAhead(ConverterStrategy converterStrategy, ref Utf8JsonReader reader, ref ReadStack state)
internal static bool SingleValueReadWithReadAhead(bool requiresReadAhead, ref Utf8JsonReader reader, ref ReadStack state)
{
bool readAhead = state.ReadAhead && converterStrategy == ConverterStrategy.Value;
bool readAhead = requiresReadAhead && state.ReadAhead;
if (!readAhead)
{
return reader.Read();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,17 @@ internal JsonConverter() { }
/// </summary>
internal virtual bool CanHaveIdMetadata => false;

/// <summary>
/// The converter supports polymorphic writes; only reserved for System.Object types.
/// </summary>
internal bool CanBePolymorphic { get; set; }

/// <summary>
/// The serializer must read ahead all contents of the next JSON value
/// before calling into the converter for deserialization.
/// </summary>
internal bool RequiresReadAhead { get; set; }

/// <summary>
/// Used to support JsonObject as an extension property in a loosely-typed, trimmable manner.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public partial class JsonConverter<T>
{
if (!state.IsContinuation)
{
if (!SingleValueReadWithReadAhead(ConverterStrategy, ref reader, ref state))
if (!SingleValueReadWithReadAhead(RequiresReadAhead, ref reader, ref state))
{
if (state.SupportContinuation)
{
Expand All @@ -51,7 +51,7 @@ public partial class JsonConverter<T>
{
// 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 (!SingleValueReadWithReadAhead(ConverterStrategy.Value, ref reader, ref state))
if (!SingleValueReadWithReadAhead(requiresReadAhead: true, ref reader, ref state))
{
state.BytesConsumed += reader.BytesConsumed;
return default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ internal sealed override bool WriteCoreAsObject(
JsonSerializerOptions options,
ref WriteStack state)
{
if (IsValueType)
if (
#if NET5_0_OR_GREATER
// Short-circuit the check against "is not null"; treated as a constant by recent versions of the JIT.
typeof(T).IsValueType)
#else
IsValueType)
#endif
{
// Value types can never have a null except for Nullable<T>.
if (value == null && Nullable.GetUnderlyingType(TypeToConvert) == null)
Expand Down
Loading