dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.83k stars 4.62k forks source link

Support reuse of JsonSerializerContext's from libraries, so as not to generate duplicate serialization code #86876

Open Simonl9l opened 1 year ago

Simonl9l commented 1 year ago

Background and motivation

As the system.text.json code generation serialization usage become more widespread, libraries will (and do) expose their serialization contexts.

If in a local project, that has a local type, that contains a property of a type for a library as above, where the local type is registered as part of the local project serialization context (via [JsonSerializable] attribute) it also generates serialization code for the libraries type.

The net effect is that there is duplicated serialization code generated for that library type and its sub components with the locally generated serialization context.

If there were a new attribute that indicated that a given type already has a code generated serializer defined in another (libraries or otherwise ) public serialization context, the local generated serialization context could hook into that library's generated serialization code for that libraries type.

It seems sensible to uses the libraries generated serialization code for its type, as built into the "released" library than regenerating that code locally, that my for some reason behave differently invalidating the implementation contract of the library.

API Proposal

public record MyLocalType
{
  [JsonPropertySerializationContext(typeof(LibrariesSerializationContext))]  public TypeInLibrary MyUsageOfLibraryType { get; set; }  
}

API Usage

As above.

Alternative Designs

None that I know of.

Risks

N/A

ghost commented 1 year ago

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

Issue Details
### Background and motivation As the `system.text.json` code generation serialization usage become more widespread, libraries will (and do) expose their serialization contexts. If in a local project, that has a local type, that contains a property of a type for a library as above, where the local type is registered as part of the local project serialization context (via `[JsonSerializable]` attribute) it also generates serialization code for the libraries type. The net effect is that there is duplicated serialization code generated for that library type and its sub components with the locally generated serialization context. If there were a new attribute that indicated that a given type already has a code generated serializer defined in another (libraries or otherwise ) public serialization context, the local generated serialization context could hook into that library's generated serialization code for that libraries type. It seems sensible to uses the libraries generated serialization code for its type, as built into the "released" library than regenerating that code locally, that my for some reason behave differently invalidating the implementation contract of the library. ### API Proposal ```csharp public record MyLocalType { [JsonPropertySerializationContext(typeof(LibrariesSerializationContext))] public TypeInLibrary MyUsageOfLibraryType { get; set; } } ``` ### API Usage As above. ### Alternative Designs None that I know of. ### Risks N/A
Author: Simonl9l
Assignees: -
Labels: `api-suggestion`, `area-System.Text.Json`, `untriaged`
Milestone: -
layomia commented 1 year ago

I believe this is already possible by adding an instance of the desired context type to the new JsonSerializerOptions.TypeInfoResolverChain property. That would use the context across the entire input type graph.

I'm not sure of the best way to achieve per-property context use like shown above, but I imagine a combination of using DefaultJsonTypeInfoResolver.Modifiers feature and consulting a static instance of of the desired context within the modifier implementation would work. You'd use desiredContext.GetTypeInfo to retrieve generated metadata & use it to populate the modifier's input JsonTypeInfo. Admittedly this is complex but an inbox API would probably work out all the kinks.

These approaches use runtime configuration (vs using declarative attributes like shown above), but the new attribute would probably use similar techniques.

We've had related discussions about resolving customization differences across assemblies (i.e. stuff specified using JsonSerializerOptions/Context/Attributes), the downsides of metadata sharing (particularly any sort of implicitly used global caches), sharing metadata for primitives across assemblies etc. There's prior art for some of these considerations with how the TypeInfoResolverChain works.