Skip to content

Commit

Permalink
implement a polymorphic serialization prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
eiriktsarpalis committed Jun 15, 2021
1 parent 9414d57 commit 8a0d317
Show file tree
Hide file tree
Showing 49 changed files with 2,856 additions and 574 deletions.
28 changes: 28 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,12 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
public bool IncludeFields { get { throw null; } set { } }
public int MaxDepth { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get { throw null; } set { } }
public System.Func<Type, bool> SupportedPolymorphicTypes { get { throw null; } set { } }
public bool PropertyNameCaseInsensitive { get { throw null; } set { } }
public System.Text.Json.JsonNamingPolicy? PropertyNamingPolicy { get { throw null; } set { } }
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get { throw null; } set { } }
public System.Collections.Generic.IList<System.Text.Json.Serialization.TypeDiscriminatorConfiguration> TypeDiscriminatorConfigurations { get { throw null; } }
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
public bool WriteIndented { get { throw null; } set { } }
public void AddContext<TContext>() where TContext : System.Text.Json.Serialization.JsonSerializerContext, new() { }
Expand Down Expand Up @@ -796,6 +798,11 @@ public sealed partial class JsonNumberHandlingAttribute : System.Text.Json.Seria
public JsonNumberHandlingAttribute(System.Text.Json.Serialization.JsonNumberHandling handling) { }
public System.Text.Json.Serialization.JsonNumberHandling Handling { get { throw null; } }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
public sealed partial class JsonPolymorphicTypeAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonPolymorphicTypeAttribute() { }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple=false)]
public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Serialization.JsonAttribute
{
Expand Down Expand Up @@ -846,6 +853,27 @@ public enum JsonUnknownTypeHandling
JsonElement = 0,
JsonNode = 1,
}
[System.AttributeUsageAttribute(System.AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
public partial class JsonKnownTypeAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonKnownTypeAttribute(System.Type subtype, string identifier) { }
public string Identifier { get { throw null; } }
public System.Type Subtype { get { throw null; } }
}
public partial class TypeDiscriminatorConfiguration : System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Type, string>>, System.Collections.Generic.IReadOnlyCollection<System.Collections.Generic.KeyValuePair<System.Type, string>>, System.Collections.IEnumerable
{
public TypeDiscriminatorConfiguration(System.Type baseType) { }
public System.Type BaseType { get { throw null; } }
int System.Collections.Generic.IReadOnlyCollection<System.Collections.Generic.KeyValuePair<System.Type, string>>.Count { get { throw null; } }
System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.Type, string>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Type, string>>.GetEnumerator() { throw null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public System.Text.Json.Serialization.TypeDiscriminatorConfiguration WithKnownType(System.Type derivedType, string identifier) { throw null; }
}
public partial class TypeDiscriminatorConfiguration<TBaseType> : System.Text.Json.Serialization.TypeDiscriminatorConfiguration where TBaseType : class
{
public TypeDiscriminatorConfiguration() : base(default(System.Type)) { }
public System.Text.Json.Serialization.TypeDiscriminatorConfiguration<TBaseType> WithKnownType<TDerivedType>(string identifier) where TDerivedType : TBaseType { throw null; }
}
public abstract partial class ReferenceHandler
{
protected ReferenceHandler() { }
Expand Down
7 changes: 6 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 @@ -88,19 +88,24 @@
<Compile Include="System\Text\Json\Serialization\Attributes\JsonIgnoreAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonIncludeAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonNumberHandlingAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPolymorphicTypeAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyNameAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonSerializableAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonMetadataServicesConverter.cs" />
<Compile Include="System\Text\Json\Serialization\IgnoreReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonKnownTypeAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\PolymorphicSerializationState.cs" />
<Compile Include="System\Text\Json\Serialization\TypeDiscriminatorConfiguration.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Converters.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoInternalOfT.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoOfT.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\TypeDiscriminatorResolver.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" />
<Compile Include="System\Text\Json\Serialization\ConfigurationList.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ArrayConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ConcurrentQueueOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ConcurrentStackOfTConverter.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Text.Json.Serialization
{
/// <summary>
/// When placed on a type, indicates that the specified subtype should
/// be serialized polymorphically using type discriminator identifiers.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
public class JsonKnownTypeAttribute : JsonAttribute
{
/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="subtype">The known subtype that should be serialized polymorphically.</param>
/// <param name="identifier">The string identifier to be used for the serialization of the subtype.</param>
public JsonKnownTypeAttribute(Type subtype, string identifier)
{
Subtype = subtype;
Identifier = identifier;
}

/// <summary>
/// The known subtype that should be serialized polymorphically.
/// </summary>
public Type Subtype { get; }

/// <summary>
/// The string identifier to be used for the serialization of the subtype.
/// </summary>
public string Identifier { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Text.Json.Serialization
{
/// <summary>
/// When placed on a type, indicates that values should
/// be serialized using the schema of their runtime types.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
public sealed class JsonPolymorphicTypeAttribute : JsonAttribute
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@
namespace System.Text.Json.Serialization
{
/// <summary>
/// A list of JsonConverters that respects the options class being immuttable once (de)serialization occurs.
/// A list of configuration items that respects the options class being immutable once (de)serialization occurs.
/// </summary>
internal sealed class ConverterList : IList<JsonConverter>
internal sealed class ConfigurationList<TItem> : IList<TItem>
{
private readonly List<JsonConverter> _list = new List<JsonConverter>();
private readonly List<TItem> _list;
private readonly JsonSerializerOptions _options;

public ConverterList(JsonSerializerOptions options)
public Action<TItem>? OnElementAdded { get; set; }

public ConfigurationList(JsonSerializerOptions options)
{
_options = options;
_list = new();
}

public ConverterList(JsonSerializerOptions options, ConverterList source)
public ConfigurationList(JsonSerializerOptions options, IList<TItem> source)
{
_options = options;
_list = new List<JsonConverter>(source._list);
_list = new List<TItem>(source is ConfigurationList<TItem> cl ? cl._list : source);
}

public JsonConverter this[int index]
public TItem this[int index]
{
get
{
Expand All @@ -47,7 +50,7 @@ public JsonConverter this[int index]

public bool IsReadOnly => false;

public void Add(JsonConverter item)
public void Add(TItem item)
{
if (item == null)
{
Expand All @@ -56,6 +59,7 @@ public void Add(JsonConverter item)

_options.VerifyMutable();
_list.Add(item);
OnElementAdded?.Invoke(item);
}

public void Clear()
Expand All @@ -64,27 +68,27 @@ public void Clear()
_list.Clear();
}

public bool Contains(JsonConverter item)
public bool Contains(TItem item)
{
return _list.Contains(item);
}

public void CopyTo(JsonConverter[] array, int arrayIndex)
public void CopyTo(TItem[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}

public IEnumerator<JsonConverter> GetEnumerator()
public IEnumerator<TItem> GetEnumerator()
{
return _list.GetEnumerator();
}

public int IndexOf(JsonConverter item)
public int IndexOf(TItem item)
{
return _list.IndexOf(item);
}

public void Insert(int index, JsonConverter item)
public void Insert(int index, TItem item)
{
if (item == null)
{
Expand All @@ -93,9 +97,10 @@ public void Insert(int index, JsonConverter item)

_options.VerifyMutable();
_list.Insert(index, item);
OnElementAdded?.Invoke(item);
}

public bool Remove(JsonConverter item)
public bool Remove(TItem item)
{
_options.VerifyMutable();
return _list.Remove(item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
int index = state.Current.EnumeratorIndex;

JsonConverter<TElement> elementConverter = GetElementConverter(ref state);
if (elementConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
if (GetElementTypeInfo(ref state).CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
{
// Fast path that avoids validation and extra indirection.
for (; index < array.Length; index++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal sealed override bool OnTryRead(
{
JsonTypeInfo elementTypeInfo = state.Current.JsonTypeInfo.ElementTypeInfo!;

if (state.UseFastPath)
if (state.UseFastPath && !state.CanContainPolymorphismMetadata)
{
// Fast path that avoids maintaining state variables and dealing with preserved references.

Expand All @@ -67,7 +67,7 @@ internal sealed override bool OnTryRead(
CreateCollection(ref reader, ref state);

_valueConverter ??= GetConverter<TValue>(elementTypeInfo);
if (_valueConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
if (elementTypeInfo.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
{
// Process all elements.
while (true)
Expand Down Expand Up @@ -132,8 +132,11 @@ internal sealed override bool OnTryRead(
}

// Handle the metadata properties.
bool preserveReferences = options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve;
if (preserveReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
bool canContainMetadata =
options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve ||
state.CanContainPolymorphismMetadata;

if (canContainMetadata && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
{
if (JsonSerializer.ResolveMetadataForJsonObject<TCollection>(ref reader, ref state, options))
{
Expand Down Expand Up @@ -188,7 +191,7 @@ internal sealed override bool OnTryRead(

state.Current.PropertyState = StackFramePropertyState.Name;

if (preserveReferences)
if (canContainMetadata)
{
ReadOnlySpan<byte> propertyName = reader.GetSpan();
if (propertyName.Length > 0 && propertyName[0] == '$')
Expand Down Expand Up @@ -278,9 +281,9 @@ internal sealed override bool OnTryWrite(
{
state.Current.ProcessedStartToken = true;
writer.WriteStartObject();
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve || state.PolymorphicTypeDiscriminator is not null)
{
if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
if (JsonSerializer.WriteMetadataForObject(this, dictionary, ref state, writer, options.ReferenceHandlingStrategy) == MetadataPropertyName.Ref)
{
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected internal override bool OnWriteResume(
_keyConverter ??= GetConverter<TKey>(typeInfo.KeyTypeInfo!);
_valueConverter ??= GetConverter<TValue>(typeInfo.ElementTypeInfo!);

if (!state.SupportContinuation && _valueConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
if (!state.SupportContinuation && typeInfo.ElementTypeInfo!.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
{
// Fast path that avoids validation and extra indirection.
do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac

if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
if (!TypeToConvert.IsAssignableFrom(typeof(List<TElement>)))
{
ThrowHelper.ThrowNotSupportedException_CannotPopulateCollection(TypeToConvert, ref reader, ref state);
}
Expand Down Expand Up @@ -96,17 +96,7 @@ protected override bool OnWriteResume(
return true;
}

internal override Type RuntimeType
{
get
{
if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface)
{
return typeof(List<TElement>);
}

return TypeToConvert;
}
}
internal override JsonTypeInfo.ConstructorDelegate? ConstructorDelegate =>
MemberAccessor.CreateConstructor<List<TElement>>(TypeToConvert);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac

if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
if (!TypeToConvert.IsAssignableFrom(typeof(Dictionary<string, object?>)))
{
ThrowHelper.ThrowNotSupportedException_CannotPopulateCollection(TypeToConvert, ref reader, ref state);
}
Expand Down Expand Up @@ -115,17 +115,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
return true;
}

internal override Type RuntimeType
{
get
{
if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface)
{
return typeof(Dictionary<string, object>);
}

return TypeToConvert;
}
}
internal override JsonTypeInfo.ConstructorDelegate? ConstructorDelegate =>
MemberAccessor.CreateConstructor<Dictionary<string, object?>>(TypeToConvert);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac

if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
if (!TypeToConvert.IsAssignableFrom(typeof(Dictionary<TKey, TValue>)))
{
ThrowHelper.ThrowNotSupportedException_CannotPopulateCollection(TypeToConvert, ref reader, ref state);
}
Expand Down Expand Up @@ -110,17 +110,7 @@ protected internal override bool OnWriteResume(
return true;
}

internal override Type RuntimeType
{
get
{
if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface)
{
return typeof(Dictionary<TKey, TValue>);
}

return TypeToConvert;
}
}
internal override JsonTypeInfo.ConstructorDelegate? ConstructorDelegate =>
MemberAccessor.CreateConstructor<Dictionary<TKey, TValue>>(TypeToConvert);
}
}
Loading

0 comments on commit 8a0d317

Please sign in to comment.