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
The current design of the JsonSerializer class exposes all serialization functionality as static methods accepting configuration parametrically (and optionally). In hindsight, this design has contributed to a few ergonomic problems when using this API:
Forgetting to pass JsonSerializerOptions to the serialization methods:
This is probably the most common issue -- I've personally fallen for this too many times.
Creating a new JsonSerializerOptions instance on each serialization:
foreach(MyPocovalueinvalues){varoptions=newJsonSerializerOptions{Converters={newJsonStringEnumConverter()}};JsonSerializer.Serialize(value,options);// recalculates JSON contracts from scratch on each iteration}
Pervasive RequiresUnreferenceCode/RequiresDynamicCode annotations: all serialization methods accepting JsonSerializerOptions have been annotated as linker/AOT-unsafe even though legitimate scenaria exist where this is not the case:
varoptions=newJsonSerializerOptions{TypeInfoResolver=MySourceGeneratedContext.Default};JsonSerializer.Serialize(value,options);// warning: JSON serialization and deserialization might require types that cannot be statically analyzed.
Bifurcation of API surface between reflection and source generation: users need to call into distinct methods depending on whether they use sourcegen or reflection. The distinction between JsonSerializerOptions and JsonSerializerContext has always been tenuous and has been rendered obsolete with the infrastructural changes introduced by Developers can customize the JSON serialization contracts of their types #63686. Arguably, the source of JSON contracts is a configuration detail that should be dealt with at the composition root and not concern any serialization methods.
API Proposal
This issue proposes we expose instance methods in JsonSerializer (or some different class):
namespaceSystem.Text.Json;publicpartialclassJsonSerializerInstance// TODO come up with a better name{publicJsonSerializerOptionsOptions{get;}publicJsonSerializer(JsonSerializerOptionsoptions);// linker-safe constructor, throws if options.TypeInfoResolver == null;// we might consider adding a linker-unsafe factory that populates TypeInfoResolver with the reflection resolver, like the existing serialization APIs do.[RequiresUnreferencedCode]publicstaticJsonSerializerDefault{get;}// serializer wrapping JsonSerializerOptions.Default/* Serialization APIs */publicstringSerialize<TValue>(TValuevalue);publicbyte[]SerializeToUtf8Bytes<TValue>(TValuevalue);publicvoidSerialize<TValue>(Utf8JsonWriterutf8Json,TValuevalue);publicvoidSerialize<TValue>(Streamutf8Json,TValuevalue);publicvoidSerializeAsync<TValue>(Streamutf8Json,TValuevalue);publicJsonDocumentSerializeToDocument<TValue>(TValuevalue);publicJsonElementSerializeToElement<TValue>(TValuevalue);publicJsonNodeSerializeToNode<TValue>(TValuevalue);publicstringSerialize(object?value,TypeinputType);publicbyte[]SerializeToUtf8Bytes(object?value,TypeinputType);publicvoidSerialize(Utf8JsonWriterwriter,object?value,TypeinputType);publicvoidSerialize(Streamutf8Json,object?value,TypeinputType);publicvoidSerializeAsync(Streamutf8Json,object?value,TypeinputType);publicJsonDocumentSerializeToDocument(object?value,TypeinputType);publicJsonElementSerializeToElement(object?value,TypeinputType);publicJsonNodeSerializeToNode(object?value,TypeinputType);/* Deserialization APIs */publicTValue?Deserialize<TValue>(stringjson);publicTValue?Deserialize<TValue>(ReadOnlySpan<char>json);publicTValue?Deserialize<TValue>(ReadOnlySpan<byte>utf8Json);publicTValue?Deserialize<TValue>(refUtf8JsonReaderreader);publicTValue?Deserialize<TValue>(Streamutf8Json);publicValueTask<TValue?>DeserializeAsync<TValue>(Streamutf8Json);publicIAsyncEnumerable<TValue?>DeserializeAsyncEnumerable<TValue>(Streamutf8Json);publicTValue?Deserialize<TValue>(JsonDocumentdocument);publicTValue?Deserialize<TValue>(JsonElementelement);publicTValue?Deserialize<TValue>(JsonNodenode);publicobject?Deserialize(ReadOnlySpan<byte>utf8Json,TypereturnType);publicobject?Deserialize(ReadOnlySpan<char>json,TypereturnType);publicobject?Deserialize(stringjson,TypereturnType);publicobject?Deserialize(JsonDocumentdocument,TypereturnType);publicobject?Deserialize(JsonElementelement,TypereturnType);publicobject?Deserialize(JsonNodenode,TypereturnType);publicobject?Deserialize(refUtf8JsonReaderreader,TypereturnType);publicobject?Deserialize(Streamutf8Json,TypereturnType);publicValueTask<object?>DeserializeAsync(Streamutf8Json,TypereturnType);}namespaceSystem.Text.Json.Serialization;publicpartialclassJsonSerializerContext{publicJsonSerializerSerializer{get;}}
JsonSerializerserializer=MyContext.Default.Serializer;serializer.Serialize(newMyPoco());// Serializes using the source generator [JsonSerializable(typeof(MyPoco))]publicpartialclassMyContext:JsonSerializerContext{}
Open Questions
Should we reuse the existing JsonSerializer class or introduce a new type?
Should we have an obsoletion plan for static APIs accepting JsonSerializerOptions?
Background & Motivation
The current design of the
JsonSerializerclass exposes all serialization functionality as static methods accepting configuration parametrically (and optionally). In hindsight, this design has contributed to a few ergonomic problems when using this API:Forgetting to pass
JsonSerializerOptionsto the serialization methods:This is probably the most common issue -- I've personally fallen for this too many times.
Creating a new
JsonSerializerOptionsinstance on each serialization:Even though this anti-pattern has been explicitly documented as a potential performance bug, users keep falling for it. We've made attempts to mitigate the problem by implementing shared metadata caches but ultimately the underlying issue still affects users. See also Add an analyzer that warns on single-use JsonSerializerOptions instances #65396 for plans an adding an analyzer in this space.
Pervasive
RequiresUnreferenceCode/RequiresDynamicCodeannotations: all serialization methods acceptingJsonSerializerOptionshave been annotated as linker/AOT-unsafe even though legitimate scenaria exist where this is not the case:Bifurcation of API surface between reflection and source generation: users need to call into distinct methods depending on whether they use sourcegen or reflection. The distinction between
JsonSerializerOptionsandJsonSerializerContexthas always been tenuous and has been rendered obsolete with the infrastructural changes introduced by Developers can customize the JSON serialization contracts of their types #63686. Arguably, the source of JSON contracts is a configuration detail that should be dealt with at the composition root and not concern any serialization methods.API Proposal
This issue proposes we expose instance methods in
JsonSerializer(or some different class):Usage Examples
Using the reflection-based serializer
Using the source generator
Open Questions
JsonSerializerclass or introduce a new type?JsonSerializerOptions?Related to #65396, #31094, #64646
cc @eerhardt @davidfowl @ericstj