-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Developers can customize the JSON serialization contracts of their types #63686
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json Issue DetailsBackground and MotivationToday, System.Text.Json provides two primary mechanisms for customizing serialization on the type level:
ProposalSystem.Text.Json already defines a JSON contract model internally, which is surfaced in public APIs via the JsonTypeInfo/JsonPropertyInfo types as opaque tokens for consumption by the source generator APIs. This is a proposal to define an Here is a provisional list of settings that could be user customizable in the contract model:
Progress
|
I would like to add one more use case for a contract system like this, that would be nice to have support for in System.Text.Json. So, for instance, if you're serializing a person, instead of:
you'd end up with: I would like to be able to serialize/deserialize this into a .NET class. Dealing with this currently in System.Text.Json, would involve making a separate converter for each object that is serialized this way, using slow runtime reflection, or coming up with my own fast reflection mechanism (which is a bit redundant, since the serializer already has this internally). In Newtonsoft.Json the situation can be handled by a single generic converter, without any direct use of reflection. This setup essentially requires:
Though if I understand it correctly, most of these should be covered by the current list of planned features. |
@bartonjs Did you consider the cost of boxing/unboxing in this API design? My concerns mainly are regarding the JsonPropertyInfo members which rely on |
Yes. The short answer is boxing structs is necessary in order for setter delegates to work as expected. See #63686 (comment) for more details. |
@Danielku15 see the Also note that internally we will use some optimizations to avoid boxing for DefaultJsonTypeInfoResolver so it should have minimal impact but we hope to improve that further and make it public in the future |
FYI: main feature branch is already merged and this will be available in preview 6 (or earlier on nightly builds). There are still some improvements planned (see #71123). Please let us know if you have any feedback |
I'm a little bit confused about how the use-case I mentioned here: #63686 (comment) could be handled with with this new system. I feel like it might be possible, but I couldn't figure it out. |
@DRKV333 unfortunately this scenario is out of scope in this release although we've had long discussion on supporting this - we've decided it's better to support less in this release than supporting everything at once in 2 releases (having said that I'd really wish we could add support for that but at the same time this feature is already large as is and our team has limited resources). Currently converter determines the JsonTypeInfoKind and also currently there is no way to write custom converter which sets it to value you want, it will always have JsonTypeInfoKind.None which means it won't use properties. FWIW you could possibly write JsonConverterFactory which uses DefaultJsonTypeInfoResolver to get properties for all types you want to serialize as PLIST - if you get into reasonable solution perhaps you will have some ideas how this could be exposed better and create API proposal which we could take into consideration for vNext. |
With the bulk of the implementation already merged in Preview 6, we should consider adding the following APIs so that all STJ attribute annotations/interfaces are mapped to the metadata model: public class JsonTypeInfo
{
// Maps to IJsonOnSerializing
public Action<object>? OnSerializing { get; set; }
// Maps to IJsonOnSerialized
public Action<object>? OnSerialized { get; set; }
// Maps to IJsonOnDeserializing
public Action<object>? OnDeserializing { get; set; }
// Maps to IJsonOnDeserialized
public Action<object>? OnDeserialized { get; set; }
}
public class JsonPropertyInfo
{
// Maps to JsonPropertyOrderAttribute
public int Order { get; set; } = 0;
// Maps to JsonExtensionDataAttribute
public bool IsExtensionData { get; set; }
} A few remarks on the above:
|
With .NET 7 work for the feature being mostly done, I have created a user story tracking contract customization work planned for .NET 8: #71967 |
Since all of the bullet points are now closed and we've shipped what we've planned I think this can be closed now. Any remaining issues should be tracked separately |
Background and Motivation
Today, System.Text.Json provides two primary mechanisms for customizing serialization on the type level:
Customizing the contract via attribute annotations. This is generally our recommended approach when making straightforward tweaks to the contract, however it comes with a few drawbacks:
JsonIgnoreAttribute
, it is impossible to add a JSON property to the contract that doesn't correspond to a .NET property.Authoring custom converters. While this mechanism is general-purpose enough to satisfy most customization requirements, it suffers from a couple of problems:
API Proposal
Usage examples
Custom resolver with constructed JsonTypeInfo
Adding support for
DataMemberAttribute
annotationsCombining resolver
Doc comparing 3 design variants with recommendation can be found here: https://gist.github.com/krwq/c61f33faccc708bfa569b3c8aebb45d6
JsonPropertyInfo vs JsonPropertyInfo<T> vs JsonPropertyInfo<TDeclaringType, TPropertyType>
We have considered different approaches here and it all boils down to perf of the property setter.
According to simple perf tests run on different combinations of declaring types and property types as well 4 different approaches of setters using setter in form of:
delegate void PropertySetter<DeclaringType, PropertyType>(ref DeclaringType obj, PropertyType val);
proves to be overall fastest. Current implementation would require a bit of work for this to be changed and such support can be added later. Given above we've decided to for a time being support only non-generic
PropertyInfo
with the slowest setter since such type already exists and corresponding setter would have to be added regardless of choice. In the futurePropertyInfo<TDeclaringType, TPropertyType>
should be added to support for the fastest possible case.Here are benchmark results: https://gist.github.com/krwq/d9d1bad3d59ff30f8db2a53a27adc755
Here is the benchmark code: https://gist.github.com/krwq/eb06529f0c99614579f84b69720ab46e
Acceptance Criteria
System.Text.Json already defines a JSON contract model internally, which is surfaced in public APIs via the JsonTypeInfo/JsonPropertyInfo types as opaque tokens for consumption by the source generator APIs. This is a proposal to define an
IContractResolver
-like construct that builds on top of the existing contract model and lets users generate custom contracts for System.Text.Json using runtime reflection.Here is a provisional list of settings that should be user customizable in the contract model:
Use cases
Open Questions
JsonTypeInfo
model amenable to supporting collection serialization? Should we consider exposing aJsonCollectionInfo : JsonTypeInfo
derived class? Related to Investigate ways to extend deserialization support for arbitrary collection types #38514.Progress
JsonProperty.ShouldSerialize
null values #71964JsonSerializerOptions.TypeInfoResolver
property should be nullable #71960https://github.com/dotnet/runtime/pull/72044/files#diff-f343f56e41ad406150ed9d35e7548f0d118dd3038c57dff2ff309e5e96331091R588
cc @steveharter @JamesNK
The text was updated successfully, but these errors were encountered: