You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Using JsonSerializerContext and source generation is a great tool for improving performance and supporting trimming and NativeAOT. However, producing shared libraries where support for trimming and NativeAOT is desired can be problematic in more complex scenarios. By definition, each JsonSerializerContext generated does two things that are important in the context of this issue. It roots all of the serializable types in the type hierarchy so they are not trimmed and it produces serialization/deserialization logic for each of them.
The difficulty arises when producing a large library with many various "areas". In this case, a library consumer may only use some areas of the library and not others, and when trimming the "areas" that are unused should also be trimmed to reduce size. If these areas each use JSON serialization, that leaves the library author with the choice to either create one very large source-generated JsonSerializerContext or to create a separate source-generated JsonSerializerContext for each area. The first option effectively can't be trimmed, since it will root every JSON-serializable class from every area. The second option, however, can result in massive duplication if there are also many shared classes between the various generated serializer contexts. This can bloat the library in terms of size, memory usage, JIT time, etc.
Proposed Solution
The proposed solution is to support inheriting source-generated contexts from one another. This is almost supported today, as this contrived example does compile:
publicclassCommonModel{publicstring?Name{get;set;}}publicclassSpecificModelA{publicGuidId{get;set;}publicCommonModel?Child{get;set;}}publicclassSpecificModelB{publicintId{get;set;}publicCommonModel?Child{get;set;}}[JsonSerializable(typeof(CommonModel))]internalpartialclass CommonSerializerContext : JsonSerializerContext;[JsonSerializable(typeof(SpecificModelA))]
internal partial class SpecificSerializerContextA : CommonSerializerContext;[JsonSerializable(typeof(SpecificModelB))]
internal partial class SpecificSerializerContextB : CommonSerializerContext;
However, the CommonModel has its JsonTypeInfo and all related serialization logic regenerated in both the specific serializer contexts. Instead, the source generator could recognize the inheritance pattern and not generate code for JsonTypeInfo<CommonModel>. The JsonSerializerOptions used by the context could use a combined ITypeInfoResolver to pull types from both contexts, or GetTypeInfo could call the parent base implementation, or some similar method could be used to resolve the JsonTypeInfo<CommonModel>.
Under this pattern, all types used by CommonSerializerContext would be rooted in all cases, but yet there would only be one instance of their serialization logic. Types related to the two specific serializer contexts would then be rooted based on where the code that uses those contexts is used. Library authors could then divide their serializer contexts logically based on how various consumers may use their library and subsequently get better support for trimming.
Concerns
One concern is handling the various options that are applied using the JsonSourceGenerationOptions attribute. If the base context and the derived context both include options, what is the desired behavior? The GenerationMode property would almost certainly need to apply only to models specifically included in that class. For other properties, the settings on the derived context may "win".
Another concern is handling a class explicitly included via JsonSerializable on the derived class that's also included in the base class. Should it be completely regenerated in the derived class? Or should a simple forwarder property be created for the TypeInfoPropertyName that still uses the base implementation?
Finally, what about inheritance between assemblies? The source generator may not have sufficient information about a base context from another assembly (especially if it's a PackageReference and not a ProjectReference) to make the necessary decisions.
The text was updated successfully, but these errors were encountered:
I find it unlikely that we would pursue such functionality, primarily because we would have to contend with separate context types in the hierarchy being generated by different versions of the source generator and potential incompatibilities arising from different generation strategies.
The other issue is that we generally want to move away from using context classes altogether, because it is a rather clunky way for registering source generated types. At some point in the future we would to replace it with better language tools (static abstract interface methods, extension interfaces, interceptors), this discussion is tracked by #90787.
Problem
Using
JsonSerializerContext
and source generation is a great tool for improving performance and supporting trimming and NativeAOT. However, producing shared libraries where support for trimming and NativeAOT is desired can be problematic in more complex scenarios. By definition, each JsonSerializerContext generated does two things that are important in the context of this issue. It roots all of the serializable types in the type hierarchy so they are not trimmed and it produces serialization/deserialization logic for each of them.The difficulty arises when producing a large library with many various "areas". In this case, a library consumer may only use some areas of the library and not others, and when trimming the "areas" that are unused should also be trimmed to reduce size. If these areas each use JSON serialization, that leaves the library author with the choice to either create one very large source-generated
JsonSerializerContext
or to create a separate source-generatedJsonSerializerContext
for each area. The first option effectively can't be trimmed, since it will root every JSON-serializable class from every area. The second option, however, can result in massive duplication if there are also many shared classes between the various generated serializer contexts. This can bloat the library in terms of size, memory usage, JIT time, etc.Proposed Solution
The proposed solution is to support inheriting source-generated contexts from one another. This is almost supported today, as this contrived example does compile:
However, the
CommonModel
has itsJsonTypeInfo
and all related serialization logic regenerated in both the specific serializer contexts. Instead, the source generator could recognize the inheritance pattern and not generate code forJsonTypeInfo<CommonModel>
. TheJsonSerializerOptions
used by the context could use a combinedITypeInfoResolver
to pull types from both contexts, orGetTypeInfo
could call the parent base implementation, or some similar method could be used to resolve theJsonTypeInfo<CommonModel>
.Under this pattern, all types used by
CommonSerializerContext
would be rooted in all cases, but yet there would only be one instance of their serialization logic. Types related to the two specific serializer contexts would then be rooted based on where the code that uses those contexts is used. Library authors could then divide their serializer contexts logically based on how various consumers may use their library and subsequently get better support for trimming.Concerns
One concern is handling the various options that are applied using the
JsonSourceGenerationOptions
attribute. If the base context and the derived context both include options, what is the desired behavior? TheGenerationMode
property would almost certainly need to apply only to models specifically included in that class. For other properties, the settings on the derived context may "win".Another concern is handling a class explicitly included via
JsonSerializable
on the derived class that's also included in the base class. Should it be completely regenerated in the derived class? Or should a simple forwarder property be created for theTypeInfoPropertyName
that still uses the base implementation?Finally, what about inheritance between assemblies? The source generator may not have sufficient information about a base context from another assembly (especially if it's a PackageReference and not a ProjectReference) to make the necessary decisions.
The text was updated successfully, but these errors were encountered: