Skip to content

Replace Unsafe.Unbox with StrongBox for JSON source-generated struct accessors - #133997

Open
jkoritzinsky wants to merge 2 commits into
mainfrom
dev/jkoritzinsky/json-no-unsafe-unbox
Open

jkoritzinsky wants to merge 2 commits into
mainfrom
dev/jkoritzinsky/json-no-unsafe-unbox

Conversation

@jkoritzinsky

@jkoritzinsky jkoritzinsky commented Sep 15, 2026

Copy link
Copy Markdown
Member

Description

This change replaces usages of Unsafe.Unbox in the System.Text.Json source generator with StrongBox for struct member getters and setters during deserialization and serialization.

Key changes:

  • Updated JsonSourceGenerator.Emitter.cs so value-type property setters, getters, [UnsafeAccessor]s, and reflection fallbacks use StrongBox instead of Unsafe.Unbox.
  • Configured source-generated value-type JsonTypeInfo instances to instantiate their target object wrapped in StrongBox.
  • Updated ObjectDefaultConverter and ObjectWithParameterizedConstructorConverter to handle StrongBox during property population and unwrap box.Value upon completing deserialization.

Note

This pull request was created with GitHub Copilot.

…accessors

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Review tier: Lite
Findings: 3 High severity · 2 Medium severity

Open (5)
What changed in this PR

This PR replaces Unsafe.Unbox<T> with StrongBox<T> for source-generated struct deserialization accessors.

Changes:

  • Adds StrongBox-aware generated getters, setters, and reflection fallbacks.
  • Wraps source-generated value-type instances during deserialization.
  • Updates object converters and compilation references.
File Description
src/​libraries/​System.Text.Json/​tests/​System.Text.Json.SourceGeneration.Unit.Tests/​CompilationHelper.cs Updated as part of this pull request.
src/​libraries/​System.Text.Json/​src/​System/​Text/​Json/​Serialization/​Metadata/​JsonTypeInfoOfT.cs Updated as part of this pull request.
src/​libraries/​System.Text.Json/​src/​System/​Text/​Json/​Serialization/​Metadata/​JsonTypeInfo.cs Updated as part of this pull request.
src/​libraries/​System.Text.Json/​src/​System/​Text/​Json/​Serialization/​Metadata/​JsonMetadataServices.Helpers.cs Updated as part of this pull request.
src/​libraries/​System.Text.Json/​src/​System/​Text/​Json/​Serialization/​Converters/​Object/​ObjectWithParameterizedConstructorConverter.cs Updated as part of this pull request.
src/​libraries/​System.Text.Json/​src/​System/​Text/​Json/​Serialization/​Converters/​Object/​ObjectDefaultConverter.cs Updated as part of this pull request.
src/​libraries/​System.Text.Json/​gen/​JsonSourceGenerator.Emitter.cs Updated as part of this pull request.

{
return typeGenerationSpec.TypeRef.IsValueType
? $"""static (obj, value) => {GetUnboxExpression(contextSpec, declaringTypeFQN)}.{propertyName} = value!"""
? $"""static (obj, value) => (({StrongBoxTypeRef}<{declaringTypeFQN}>)obj).Value.{propertyName} = value!"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I concur, this is a breaking change unfortunately. Users can access, modify, or wrap the delegate directly via the JsonPropertyInfo.Set property and anybody doing so today is relying on the current implicit contract.

…mpiler warnings

- Ensure JsonTypeInfo<T>.CreateObject continues returning boxed T instead of StrongBox<T>
- Wrap CreateObject return values and pre-populated values in StrongBox<T> in object converters
- Support custom CreateObject modifier delegates for source-generated structs
- Preserve struct mutations in deserialization callbacks by unboxing and copying back
- Add tests for CreateObject return types, custom modifiers, and struct callbacks

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 15, 2026 23:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment on lines +43 to +45
obj = IsValueType && jsonTypeInfo.IsSourceGenerated && state.Current.ReturnValue is not StrongBox<T>
? new StrongBox<T>((T)state.Current.ReturnValue!)
: state.Current.ReturnValue!;
private static JsonTypeInfo<T> CreateCore<T>(JsonConverter converter, JsonSerializerOptions options)
{
var typeInfo = new JsonTypeInfo<T>(converter, options);
typeInfo.IsSourceGenerated = true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants