Skip to content

Add [JsonConstructor] and support for deserializing with parameterized ctors #33444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 20, 2020
Merged
5 changes: 5 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 @@ -753,6 +753,11 @@ protected internal JsonConverter() { }
public abstract T Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options);
public abstract void Write(System.Text.Json.Utf8JsonWriter writer, [System.Diagnostics.CodeAnalysis.DisallowNull] T value, System.Text.Json.JsonSerializerOptions options);
}
[System.AttributeUsageAttribute(System.AttributeTargets.Constructor, AllowMultiple = false)]
public sealed partial class JsonConstructorAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonConstructorAttribute() { }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
public sealed partial class JsonExtensionDataAttribute : System.Text.Json.Serialization.JsonAttribute
{
Expand Down
19 changes: 17 additions & 2 deletions src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@
<data name="JsonSerializerDoesNotSupportComments" xml:space="preserve">
<value>Comments cannot be stored when deserializing objects, only the Skip and Disallow comment handling modes are supported.</value>
</data>
<data name="DeserializeMissingParameterlessConstructor" xml:space="preserve">
<value>Deserialization of reference types without parameterless constructor is not supported. Type '{0}'</value>
<data name="DeserializeMissingDeserializationConstructor" xml:space="preserve">
<value>Deserialization of reference types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with '{0}' is not supported. Type '{1}'</value>
</data>
<data name="DeserializePolymorphicInterface" xml:space="preserve">
<value>Deserialization of interface types is not supported. Type '{0}'</value>
Expand Down Expand Up @@ -482,10 +482,25 @@
<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>
<data name="MultipleMembersBindWithConstructorParameter" xml:space="preserve">
<value>Members '{0}' and '{1}' on type '{2}' cannot both bind with parameter '{3}' in constructor '{4}' on deserialization.</value>
</data>
<data name="ConstructorParamIncompleteBinding" xml:space="preserve">
<value>Each parameter in constructor '{0}' on type '{1}' must bind to an object member on deserialization. Each parameter name must be the camel case equivalent of an object member named with the pascal case naming convention.</value>
</data>
<data name="ConstructorMaxOf64Parameters" xml:space="preserve">
<value>The constructor '{0}' on type '{1}' may not have more than 64 parameters for deserialization.</value>
</data>
<data name="ObjectWithParameterizedCtorRefMetadataNotHonored" xml:space="preserve">
<value>Reference metadata is not honored when deserializing types using parameterized constructors. See type '{0}'.</value>
</data>
<data name="SerializerConverterFactoryReturnsNull" xml:space="preserve">
<value>The converter '{0}' cannot return a null value.</value>
</data>
<data name="SerializationNotSupportedParentType" xml:space="preserve">
<value>The unsupported member type is located on type '{0}'.</value>
</data>
<data name="ExtensionDataCannotBindToCtorParam" xml:space="preserve">
<value>The extension data property '{0}' on type '{1}' cannot bind with a parameter in constructor '{2}'.</value>
</data>
</root>
13 changes: 11 additions & 2 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>$(OutputPath)$(MSBuildProjectName).xml</DocumentationFile>
Expand Down Expand Up @@ -53,7 +53,9 @@
<Compile Include="System\Text\Json\Reader\Utf8JsonReader.cs" />
<Compile Include="System\Text\Json\Reader\Utf8JsonReader.MultiSegment.cs" />
<Compile Include="System\Text\Json\Reader\Utf8JsonReader.TryGet.cs" />
<Compile Include="System\Text\Json\Serialization\Arguments.cs" />
<Compile Include="System\Text\Json\Serialization\ClassType.cs" />
<Compile Include="System\Text\Json\Serialization\ArgumentState.cs" />
<Compile Include="System\Text\Json\Serialization\ConverterList.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ArrayConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ConcurrentQueueOfTConverter.cs" />
Expand All @@ -80,6 +82,9 @@
<Compile Include="System\Text\Json\Serialization\Converters\Collection\StackOfTConverter.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" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectWithParameterizedConstructorConverter.Large.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectWithParameterizedConstructorConverter.Small.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\BooleanConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\ByteArrayConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\ByteConverter.cs" />
Expand Down Expand Up @@ -111,9 +116,10 @@
<Compile Include="System\Text\Json\Serialization\DefaultReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\JsonAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.Cache.cs" />
<Compile Include="System\Text\Json\Serialization\JsonCollectionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConstructorAttribute.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" />
Expand All @@ -127,6 +133,8 @@
<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\Serialization\JsonParameterInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonParameterInfoOfT.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoOfTTypeToConvert.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyNameAttribute.cs" />
Expand All @@ -151,6 +159,7 @@
<Compile Include="System\Text\Json\Serialization\MemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\MetadataPropertyName.cs" />
<Compile Include="System\Text\Json\Serialization\PooledByteBufferWriter.cs" />
<Compile Include="System\Text\Json\Serialization\ParameterRef.cs" />
<Compile Include="System\Text\Json\Serialization\PropertyRef.cs" />
<Compile Include="System\Text\Json\Serialization\ReadStack.cs" />
<Compile Include="System\Text\Json\Serialization\ReadStackFrame.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,12 @@ internal static class JsonConstants
public const int LowSurrogateStartValue = 0xDC00;
public const int LowSurrogateEndValue = 0xDFFF;
public const int BitShiftBy10 = 0x400;

// The maximum number of parameters a constructor can have where it can be considered
// for a path on deserialization where we don't box the constructor arguments.
public const int UnboxedParameterCountThreshold = 4;

// The maximum number of parameters a constructor can have where it can be supported.
public const int MaxParameterCount = 64;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;

using FoundProperties = System.ValueTuple<System.Text.Json.JsonPropertyInfo, System.Text.Json.JsonReaderState, long, byte[]?, string?>;
using FoundPropertiesAsync = System.ValueTuple<System.Text.Json.JsonPropertyInfo, object?, string?>;

namespace System.Text.Json
{
/// <summary>
/// Holds relevant state when deserializing objects with parameterized constructors.
/// Lives on the current ReadStackFrame.
/// </summary>
internal class ArgumentState
{
// Cache for parsed constructor arguments.
public object Arguments = null!;

// When deserializing objects with parameterized ctors, the properties we find on the first pass.
public FoundProperties[]? FoundProperties;

// When deserializing objects with parameterized ctors asynchronously, the properties we find on the first pass.
public FoundPropertiesAsync[]? FoundPropertiesAsync;
public int FoundPropertyCount;

// Current constructor parameter value.
public JsonParameterInfo? JsonParameterInfo;

// For performance, we order the parameters by the first deserialize and PropertyIndex helps find the right slot quicker.
public int ParameterIndex;
public List<ParameterRef>? ParameterRefCache;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Text.Json
{
/// <summary>
/// Constructor arguments for objects with parameterized ctors with less than 5 parameters.
/// This is to avoid boxing for small, immutable objects.
/// </summary>
internal sealed class Arguments<TArg0, TArg1, TArg2, TArg3>
{
public TArg0 Arg0 = default!;
public TArg1 Arg1 = default!;
public TArg2 Arg2 = default!;
public TArg3 Arg3 = default!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new List<TElement>();
Expand All @@ -37,7 +37,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (classInfo.CreateObject == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

TCollection returnValue = (TCollection)classInfo.CreateObject()!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ protected override void CreateCollection(ref ReadStack state)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new Dictionary<string, object>();
Expand All @@ -41,7 +41,7 @@ protected override void CreateCollection(ref ReadStack state)
{
if (classInfo.CreateObject == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

TCollection returnValue = (TCollection)classInfo.CreateObject()!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected override void CreateCollection(ref ReadStack state)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new Dictionary<string, TValue>();
Expand All @@ -40,7 +40,7 @@ protected override void CreateCollection(ref ReadStack state)
{
if (classInfo.CreateObject == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

TCollection returnValue = (TCollection)classInfo.CreateObject()!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new List<object?>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new List<TElement>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti

if (constructorDelegate == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = constructorDelegate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new List<object?>();
Expand All @@ -36,7 +36,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (classInfo.CreateObject == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

TCollection returnValue = (TCollection)classInfo.CreateObject()!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new List<TElement>();
Expand All @@ -37,7 +37,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (classInfo.CreateObject == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

TCollection returnValue = (TCollection)classInfo.CreateObject()!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected override void CreateCollection(ref ReadStack state)
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new Dictionary<string, TValue>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (!TypeToConvert.IsAssignableFrom(RuntimeType))
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

state.Current.ReturnValue = new HashSet<TElement>();
Expand All @@ -34,7 +34,7 @@ protected override void CreateCollection(ref ReadStack state, JsonSerializerOpti
{
if (classInfo.CreateObject == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
}

TCollection returnValue = (TCollection)classInfo.CreateObject()!;
Expand Down
Loading