Skip to content
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

[System.Text.Json] Expose additional metadata in contract APIs. #102078

Open
Tracked by #100159
eiriktsarpalis opened this issue May 10, 2024 · 3 comments
Open
Tracked by #100159

[System.Text.Json] Expose additional metadata in contract APIs. #102078

eiriktsarpalis opened this issue May 10, 2024 · 3 comments
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Text.Json blocking Marks issues that we want to fast track in order to unblock other important work
Milestone

Comments

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented May 10, 2024

Background and motivation

Contributes to #100159. The implementation of the https://github.com/eiriktsarpalis/stj-schema-mapper prototype revealed a number of gaps in the existing STJ contract metadata APIs which currently need to be filled manually using reflection or need to be extracted from private fields in the STJ implementation itself.

This proposal includes all the APIs necessary to make schema-related metadata available to end-users looking to export STJ schemas for .NET types, both for reflection and the source generator. The primary motivation is giving access to attribute metadata, via the ICustomAttributeProvider abstraction in a way that works well for both reflection and source generators/trimmed apps without compromising startup time or application size.

API Proposal

namespace System.Text.Json.Serialization.Metadata;

public partial class JsonTypeInfo
{
+   // Attribute provider for the deserialization constructor (aka `JsonConstructorAttribute` ctors)   
+   public ICustomAttributeProvider? ConstructorAttributeProvider { get; }
}

public partial class JsonPropertyInfo
{
+   public Type DeclaringType { get; }
+   // The JsonParameterInfo that has been associated with the current property.
+   public JsonParameterInfo? AssociatedParameterInfo { get; }
    public ICustomAttributeProvider? AttributeProvider { get; set; }
}

// Currently internal type, exposed as a read-only façade for now.
+public class JsonParameterInfo
+{
+    public Type DeclaringType { get; }
+    public int Position { get; }
+    public Type ParameterType { get; }
+    public bool HasDefaultValue { get; }
+    public object? DefaultValue { get; }
+    public bool DisallowNull { get; }
+    public ICustomAttributeProvider? AttributeProvider { get; }
+}

// New APIs for SG-specific APIs
[EditorBrowsable(EditorBrowsable.Never)]
public partial class JsonObjectInfoValues<T>
{
+    public Func<ICustomAttributeProvider?>? ConstructorAttributeProviderFactory { get; init; }
}

[EditorBrowsable(EditorBrowsable.Never)]
public partial class JsonPropertyInfoValues<T>
{
+    public Func<ICustomAttributeProvider?>? AttributeProviderFactory { get; init; }
}

[EditorBrowsable(EditorBrowsableState.Never)]
public partial class JsonParameterInfoValues
{
+    public bool DisallowNull { get; init; }
}

API Usage

Consider the user defined-type:

public class MyPoco
{
    [MyCustomAttribute]
    public int Value { get; set; }
}

Then the source generator would be able to give access to property attributes by generating the following code:

new JsonPropertyInfoValues<int>
{
    AttributeProviderFactory = static () => typeof(MyPoco).GetProperty(nameof(MyPoco.Value), BindingFlags.Public | BindingFlags.Instance),
}

Factoring the attribute provider resolution logic into a delegate that uses a reflection literal expression this ensures that

  1. we have delayed, pay-for-play reflection-based lookups of attribute metadata,
  2. the delegate can be trimmed if not consumed in user code and
  3. The trimmer doesn't remove attribute metadata for the specified members.

cc @Jozkee @stephentoub @eerhardt @captainsafia

@eiriktsarpalis eiriktsarpalis added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label May 10, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label May 10, 2024
Copy link
Contributor

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

@eiriktsarpalis eiriktsarpalis removed the untriaged New issue has not been triaged by the area owner label May 10, 2024
@eiriktsarpalis eiriktsarpalis self-assigned this May 10, 2024
@eiriktsarpalis eiriktsarpalis added api-ready-for-review API is ready for review, it is NOT ready for implementation blocking Marks issues that we want to fast track in order to unblock other important work and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels May 10, 2024
@eiriktsarpalis eiriktsarpalis added this to the 9.0.0 milestone May 10, 2024
Copy link
Contributor

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

@terrajobst
Copy link
Member

terrajobst commented May 21, 2024

Video

  • Looks good as proposed
  • JsonPropertyInfo
    • Let's rename AssociatedParameterInfo to just AssociatedParameter
  • JsonParameterInfo
    • Let's make it sealed
namespace System.Text.Json.Serialization.Metadata;

public partial class JsonTypeInfo
{
    // Attribute provider for the deserialization constructor (aka `JsonConstructorAttribute` ctors)   
    public ICustomAttributeProvider? ConstructorAttributeProvider { get; }
}

public partial class JsonPropertyInfo
{
    public Type DeclaringType { get; }
    // The JsonParameterInfo that has been associated with the current property.
    public JsonParameterInfo? AssociatedParameter { get; }

    // Existing
    // public ICustomAttributeProvider? AttributeProvider { get; set; }
}

// Currently internal type, exposed as a read-only façade for now.
public sealed class JsonParameterInfo
{
    public Type DeclaringType { get; }
    public int Position { get; }
    public Type ParameterType { get; }
    public bool HasDefaultValue { get; }
    public object? DefaultValue { get; }
    public bool DisallowNull { get; }
    public ICustomAttributeProvider? AttributeProvider { get; }
}

// New APIs for SG-specific APIs
public partial class JsonObjectInfoValues<T>
{
    public Func<ICustomAttributeProvider?>? ConstructorAttributeProviderFactory { get; init; }
}

public partial class JsonPropertyInfoValues<T>
{
    public Func<ICustomAttributeProvider?>? AttributeProviderFactory { get; init; }
}

public partial class JsonParameterInfoValues
{
    public bool DisallowNull { get; init; }
}

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented area-System.Text.Json blocking Marks issues that we want to fast track in order to unblock other important work
Projects
None yet
Development

No branches or pull requests

2 participants