Skip to content

Fix JsonException message property type info in class parameterized constructor#126575

Open
XeronOwO wants to merge 3 commits intodotnet:mainfrom
XeronOwO:fix_json_exception_type
Open

Fix JsonException message property type info in class parameterized constructor#126575
XeronOwO wants to merge 3 commits intodotnet:mainfrom
XeronOwO:fix_json_exception_type

Conversation

@XeronOwO
Copy link
Copy Markdown

@XeronOwO XeronOwO commented Apr 6, 2026

This pull request fixes an incorrect type hint in the thrown JsonException when deserialization fails for classes with parameterized constructors.

Reproduction

using System.Text.Json;

Test<NormalClass>();
Test<ParameterizedNormalClass>();
Test<RecordClass>();
Test<NormalStruct>();
Test<ParameterizedNormalStruct>();
Test<RecordStruct>();

static void Test<T>()
{
	try
	{
		JsonSerializer.Deserialize<T>("{\"Text\":{}}"); // create json convert exception manually
	}
	catch (Exception e)
	{
		Console.WriteLine(e.Message);
	}
}

class NormalClass
{
	public string Text { get; set; } = null!;
}

class ParameterizedNormalClass(string text)
{
	public string Text { get; set; } = text;
}

record RecordClass(string Text);

struct NormalStruct()
{
	public string Text { get; set; } = null!;
}

struct ParameterizedNormalStruct(string text)
{
	public string Text { get; set; } = text;
}

record struct RecordStruct(string Text);

Expected Output

The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.

Actual Output

The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to ParameterizedNormalClass. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to RecordClass. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.
The JSON value could not be converted to System.String. Path: $.Text | LineNumber: 0 | BytePositionInLine: 9.

Root Cause

For classes with parameterized constructors, state.Current.CtorArgumentState.JsonParameterInfo was used instead of state.Current.JsonPropertyInfo.
(All structs, including parameterized structs and record structs, work correctly without this issue.)

jsonParameterInfo = jsonPropertyInfo.AssociatedParameter;
if (jsonParameterInfo != null)
{
state.Current.JsonPropertyInfo = null;
state.Current.CtorArgumentState!.JsonParameterInfo = jsonParameterInfo;
state.Current.NumberHandling = jsonParameterInfo.NumberHandling;

As a result, the exception message fell back to state.Current.JsonTypeInfo.Type for affected classes.

// Use a default message.
Type propertyType = state.Current.JsonPropertyInfo?.PropertyType ?? state.Current.JsonTypeInfo.Type;
message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType);

Fix

Added logic to retrieve type information from state.Current.CtorArgumentState.JsonParameterInfo.ParameterType, ensuring correct type resolution for classes with parameterized constructors.

@danmoseley
Copy link
Copy Markdown
Member

Does this need a test?

@XeronOwO
Copy link
Copy Markdown
Author

@dotnet-policy-service agree

@XeronOwO
Copy link
Copy Markdown
Author

@danmoseley Yes, this needs a test. I've added a regression test to ConstructorTests.Exceptions.cs that verifies the JsonException message contains the correct property type (System.String) rather than the declaring class type (ParameterizedNormalClass / RecordClass) when deserialization fails for classes with parameterized constructors.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes JsonException default message type reporting during deserialization failures for object types using parameterized constructors by resolving the failing member type from the active constructor parameter (when JsonPropertyInfo is unavailable).

Changes:

  • Update ThrowHelper.AddJsonExceptionInformation to prefer state.Current.CtorArgumentState?.JsonParameterInfo?.ParameterType when JsonPropertyInfo is null.
  • Add a regression test ensuring parameterized class/record constructor failures report the property/parameter type (System.String) rather than the declaring type.
Show a summary per file
File Description
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs Adjusts default exception type resolution to use active constructor parameter type when applicable.
src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs Adds coverage validating the corrected exception message type for parameterized constructors.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 0

…tructors

This test verifies that when deserialization fails for classes with
parameterized constructors, the JsonException message reports the
correct property type (e.g., System.String) rather than the declaring
class type.

Covers both parameterized classes and records to ensure the fix in
ThrowHelper.Serialization.cs works correctly across different scenarios.
…essage test

Make test classes public and register them in source generation contexts
to fix compilation errors in System.Text.Json.SourceGeneration.Tests.

The test classes need to be public (not private) so they can be referenced
by the JsonSerializable attributes in the source generation test contexts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Text.Json community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants