Use UnsafeAccessor/reflection for inaccessible members in source-generated JsonTypeInfo#124650
Use UnsafeAccessor/reflection for inaccessible members in source-generated JsonTypeInfo#124650
Conversation
…TypeInfo Remove non-required init-only properties from the constructor delegate's object initializer expression. Instead, generate real setters using UnsafeAccessor (NET8+) or cached reflection (older TFMs) so that C# default values are preserved when properties are absent from JSON. Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
eiriktsarpalis
left a comment
There was a problem hiding this comment.
@copilot Let's try to broaden the scope of this PR a little bit. We can also use unsafe accessors (or the reflection fall-back) to finally enable JsonIncludeAttribute support in the source generator. If a member is marked JsonInclude but is not accessible by the current generation context, we instruct the source generator to emit getter and setter delegates using unsafe accessors.
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
…flection, JsonInclude support, rename to requiredMembers - Per-property unsafe accessor check: generic types can't use UnsafeAccessor - Lazy PropertyInfo initialization via Lazy<T> for reflection fallback - Enable JsonInclude for inaccessible members via unsafe accessors/reflection - Rename memberInitializerNames to requiredMemberNames - Add IsGenericType to TypeRef for per-property checks - Generate getter accessors for inaccessible JsonInclude properties Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Broadened scope — |
…cType from TypeRef Move the per-property unsafe accessor eligibility check from the emitter to the property model. The parser now computes CanUseUnsafeAccessors by checking both UnsafeAccessorAttribute availability and whether the declaring type is generic, using the type symbol directly. This removes IsGenericType from TypeRef, IsUnsafeAccessorsSupported from ContextGenerationSpec, and the _isUnsafeAccessorsSupported field from the Emitter. Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
…reflection fallback Use a nullable PropertyInfo? static field with ??= lazy initialization in a helper method, following the PolyType pattern. This avoids introducing a Lazy<T> type dependency. Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
Replace PropertyInfo caching with Func/Action delegate caching for the reflection fallback path. Instead of calling PropertyInfo.GetValue/SetValue on each access, we now cache PropertyInfo.GetValue and PropertyInfo.SetValue as Func<object?, object?> and Action<object?, object?> delegates respectively, which provides faster subsequent access. Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
|
@copilot please address the merge conflicts. |
Resolved merge conflict in System.Text.Json.SourceGeneration.Tests.targets: - Kept SYSLIB1037 and SYSLIB1226 from main - Excluded SYSLIB1038 and SYSLIB1222 (no longer emitted by our changes) All 7805 source gen tests pass after merge. Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
|
@copilot please address the most recent merge conflicts |
Resolved conflicts from main's addition of JsonNamingPolicyAttribute: - Parser.cs: Added typeNamingPolicy parameter to ProcessTypeAttributes, ParsePropertyGenerationSpecs, and related methods - DefaultJsonTypeInfoResolver.Helpers.cs: Added typeNamingPolicy parameter to AddMembersDeclaredBySuperType and CreatePropertyInfo - PropertyVisibilityTests.cs: Kept our await base.X() delegation since inaccessible [JsonInclude] properties are now supported - SourceGeneration.Tests.targets: Kept SYSLIB1038/SYSLIB1222 excluded since these diagnostics are no longer emitted - Non-STJ conflicts (gc.cpp, crsttypes_generated.h, etc.): took main Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Merged with main and resolved all conflicts in f7275aa. Key resolutions:
|
|
@copilot there still appear to be merge conflicts. |
…alues-json-serialization-again # Conflicts: # src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs # src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets
Description
The source generator uses
[UnsafeAccessor](NET8+ non-generic types) or cached reflection delegates (older targets/generic types) to access inaccessible members, enabling three previously unsupported scenarios:1. Preserve default values for init-only properties
The source generator previously set init-only properties via the constructor delegate's object initializer expression, even when absent from JSON. This replaced C# property initializer defaults with
default(T):Init-only properties are now removed from the constructor delegate and set post-construction via UnsafeAccessor or reflection, only when present in JSON.
2. Enable
[JsonInclude]for inaccessible propertiesPreviously,
[JsonInclude]properties with inaccessible getters or setters generated throwing delegates. They are now fully supported:3. Enable
[JsonConstructor]for inaccessible constructorsPreviously, inaccessible constructors marked with
[JsonConstructor]were ignored with a SYSLIB1222 warning. They are now invoked via[UnsafeAccessor(UnsafeAccessorKind.Constructor)]or cachedConstructorInfo.Invokereflection fallback:Generated Code Samples
UnsafeAccessor path (NET8+ non-generic types)
For a type with a private
[JsonInclude]property and an init-only property:The source generator emits
[UnsafeAccessor]extern methods that are invoked directly from the getter/setter delegates.JsonPropertyInfoValues<T>is generic on the property type, so theSetterdelegate isAction<object, T?>and theGetterisFunc<object, T?>. The only cast in each delegate is on theobjectparameter to the declaring type:Property getter (inaccessible
[JsonInclude])Property setter (inaccessible
[JsonInclude])Init-only property setter
Constructor (inaccessible
[JsonConstructor])Value types (structs)
For struct types, the extern uses
refand the delegate passesref Unsafe.Unbox<T>(obj):Reflection fallback (older targets or generic types)
When
[UnsafeAccessor]is not available (e.g., netstandard2.0 targets) or the declaring type is generic (not supported by UnsafeAccessor), the generator falls back to cached reflection with strongly typed wrappers:Property getter (reflection)
Property setter (reflection)
Field accessor (reflection)
For fields,
FieldInfois cached directly since fields don't haveMethodInfoequivalents:Constructor (reflection)
Note: Unlike
MethodInfo,ConstructorInfocannot be wrapped in a delegate, so theConstructorInfoitself is cached:Changes
Parser: Non-required init-only properties removed from
PropertyInitializerSpecs(renamedmemberInitializerNamestorequiredMemberNames). Inaccessible[JsonInclude]properties no longer flagged asHasInvalidConfigurationForFastPath. Inaccessible[JsonConstructor]constructors no longer nulled out — tracked as inaccessible for the emitter.PropertyGenerationSpec: Added
CanUseUnsafeAccessors(per-property, checksUnsafeAccessorAttributeavailability and non-generic declaring type),NeedsAccessorForGetter, andNeedsAccessorForSetter.TypeGenerationSpec: Added
ConstructorIsInaccessibleandCanUseUnsafeAccessorForConstructor.Emitter: Unified accessor pattern with identical wrapper signatures for both paths. UnsafeAccessor externs are implementation details. Struct types use
ref Unsafe.Unbox<T>(obj). Fast-path serialization includes inaccessible[JsonInclude]properties. Field reflection fallback correctly cachesFieldInfoand calls.GetValue()/.SetValue().KnownTypeSymbols: Detect
UnsafeAccessorAttributeavailability.Runtime validation (
JsonMetadataServices.Helpers.cs): Allow non-public[JsonInclude]properties when getter/setter delegates are provided.Diagnostics: SYSLIB1038 and SYSLIB1222 no longer emitted for these scenarios.
Tests: Updated overrides, added new test types and methods including generic type coverage for the reflection fallback path (
GenericClassWithPrivateJsonIncludeProperties<T>). All 7805 source gen tests pass.🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.