Skip to content

Commit 897b2a3

Browse files
krwqeiriktsarpalis
andauthored
JSON contract customization (dotnet#70435)
* Initial implementation for contract customization fix build errors Move converter rooting to DefaultJsonTypeInfoResolver so that it can be used standalone Fix ConfigurationList.IsReadOnly Minor refactorings (#1) * Makes the following changes: * Move singleton initialization for DefaultTypeInfoResolver behind a static property. * Consolidate JsonSerializerContext & IJsonTypeInfoResolver values to a single field. * Move reflection fallback logic away from JsonSerializerContext and into JsonSerializerOptions * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs * remove testing of removed field Simplify the JsonTypeInfo.CreateObject implemenetation (#2) * Simplify the JsonTypeInfo.CreateObject implemenetation * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs * Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs Co-authored-by: Krzysztof Wicher <[email protected]> Co-authored-by: Krzysztof Wicher <[email protected]> Tests and fixes for JsonTypeInfoKind.None TypeInfo type mismatch tests Allow setting NumberHandling on JsonTypeInfoKind.None test resolver returning wrong type of options JsonTypeInfo/JsonPropertyInfo mutability tests rename test file Move default converter rooting responsibility behind DefaultJsonTypeInfoResolver (#3) * Move default converter rooting responsibility behind DefaultJsonTypeInfoResolver * address feedback Add simple test for using JsonTypeInfo<T> with APIs directly taking it fix and tests for untyped/typed CreateObject uncomment test cases, remove todo More tests and tiny fixes Add a JsonTypeInfoResolver.Combine test for JsonSerializerContext (#4) * Fix JsonTypeInfoResolver.Combine for JsonSerializerContext * Break up failing test Fix simple scenarios for combining contexts (#6) * Fix simple scenarios for combining contexts * feedback JsonSerializerContext combine test with different camel casing Remove unneeded virtual calls & branching when accessing Get & Set delegates (#7) JsonPropertyInfo tests everything minus ShouldSerialize & NumberHandling Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs throw InvalidOperationException rather than ArgumentNullException for source gen when PropertyInfo.Name is assigned through JsonPropertyInfoValues tests for duplicated property names and JsonPropertyInfo.NumberHandling Add tests for NumberHandling and failing tests for ShouldSerialize disable the failing test and add extra checks disable remainder of the failing ShouldSerialize tests, fix working one Fix ShouldSerialize and IgnoreCondition interop Add failing tests for CreateObject + parametrized constructors Fix CreateObject support for JsonConstructor types (#10) * Fix CreateObject support for JsonConstructor types * address feedback Make contexts more combinator friendly (#9) * Make contexts more combinator friendly * remove converter cache * redesign test to account for JsonConstructorAttribute * Combine unit tests * address feedback * Add acceptance tests for DataContract attributes & Specified pattern (#11) * Add private field serialization acceptance test (#13) * tests, PR feedback (#14) * PR feedback and extra tests * Shorten class name, remove incorrect check (not true for polimorphic cases) * Make parameter matching for custom properties map property Name with parameter (#16) * Test static initialization with JsonTypeInfo (#17) * Fix test failures and proper fix this time (#18) * Fix test failures and proper fix this time * reinstate ActiveIssueAttribute * PR feedback and adjust couple of tests which don't set TypeInfoResolver * fix IAsyncEnumerable tests * Lock JsonSerializerOptions in JsonTypeInfo.EnsureConfigured() Co-authored-by: Eirik Tsarpalis <[email protected]> Co-authored-by: Eirik Tsarpalis <[email protected]>
1 parent de078d1 commit 897b2a3

File tree

76 files changed

+5865
-927
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+5865
-927
lines changed

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

Lines changed: 109 additions & 137 deletions
Large diffs are not rendered by default.

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,11 +1473,11 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor)
14731473

14741474
if (forType)
14751475
{
1476-
return $"{Emitter.GetConverterFromFactoryMethodName}(typeof({type.GetCompilableName()}), new {converterType.GetCompilableName()}())";
1476+
return $"{Emitter.GetConverterFromFactoryMethodName}({OptionsLocalVariableName}, typeof({type.GetCompilableName()}), new {converterType.GetCompilableName()}())";
14771477
}
14781478
else
14791479
{
1480-
return $"{Emitter.JsonContextVarName}.{Emitter.GetConverterFromFactoryMethodName}<{type.GetCompilableName()}>(new {converterType.GetCompilableName()}())";
1480+
return $"{Emitter.GetConverterFromFactoryMethodName}<{type.GetCompilableName()}>({OptionsLocalVariableName}, new {converterType.GetCompilableName()}())";
14811481
}
14821482
}
14831483

src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ internal sealed class TypeGenerationSpec
3131
/// </summary>
3232
public string TypeInfoPropertyName { get; set; }
3333

34+
/// <summary>
35+
/// Method used to generate JsonTypeInfo given options instance
36+
/// </summary>
37+
public string CreateTypeInfoMethodName => $"Create_{TypeInfoPropertyName}";
38+
3439
public JsonSourceGenerationMode GenerationMode { get; set; }
3540

3641
public bool GenerateMetadata => GenerationModeIsSpecified(JsonSourceGenerationMode.Metadata);

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,13 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
357357
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
358358
public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get { throw null; } set { } }
359359
public System.Collections.Generic.IList<System.Text.Json.Serialization.JsonPolymorphicTypeConfiguration> PolymorphicTypeConfigurations { get { throw null; } }
360+
public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver TypeInfoResolver
361+
{
362+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
363+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.")]
364+
get { throw null; }
365+
set { }
366+
}
360367
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
361368
public bool WriteIndented { get { throw null; } set { } }
362369
public void AddContext<TContext>() where TContext : System.Text.Json.Serialization.JsonSerializerContext, new() { }
@@ -950,12 +957,13 @@ public JsonSerializableAttribute(System.Type type) { }
950957
public string? TypeInfoPropertyName { get { throw null; } set { } }
951958
public System.Text.Json.Serialization.JsonSourceGenerationMode GenerationMode { get { throw null; } set { } }
952959
}
953-
public abstract partial class JsonSerializerContext
960+
public abstract partial class JsonSerializerContext : System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver
954961
{
955962
protected JsonSerializerContext(System.Text.Json.JsonSerializerOptions? options) { }
956963
protected abstract System.Text.Json.JsonSerializerOptions? GeneratedSerializerOptions { get; }
957964
public System.Text.Json.JsonSerializerOptions Options { get { throw null; } }
958965
public abstract System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(System.Type type);
966+
System.Text.Json.Serialization.Metadata.JsonTypeInfo System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver.GetTypeInfo(Type type, JsonSerializerOptions options) { throw null; }
959967
}
960968
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple = false)]
961969
public sealed partial class JsonSourceGenerationOptionsAttribute : System.Text.Json.Serialization.JsonAttribute
@@ -1044,6 +1052,20 @@ protected ReferenceResolver() { }
10441052
}
10451053
namespace System.Text.Json.Serialization.Metadata
10461054
{
1055+
public class DefaultJsonTypeInfoResolver : System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver
1056+
{
1057+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
1058+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.")]
1059+
public DefaultJsonTypeInfoResolver() { }
1060+
1061+
public virtual System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo(System.Type type, System.Text.Json.JsonSerializerOptions options) { throw null; }
1062+
1063+
public System.Collections.Generic.IList<System.Action<System.Text.Json.Serialization.Metadata.JsonTypeInfo>> Modifiers { get; }
1064+
}
1065+
public interface IJsonTypeInfoResolver
1066+
{
1067+
System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(System.Type type, System.Text.Json.JsonSerializerOptions options);
1068+
}
10471069
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
10481070
public sealed partial class JsonCollectionInfoValues<TCollection>
10491071
{
@@ -1116,6 +1138,7 @@ public static partial class JsonMetadataServices
11161138
public static System.Text.Json.Serialization.JsonConverter<T> GetUnsupportedTypeConverter<T>() { throw null; }
11171139
public static System.Text.Json.Serialization.JsonConverter<T> GetEnumConverter<T>(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; }
11181140
public static System.Text.Json.Serialization.JsonConverter<T?> GetNullableConverter<T>(System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> underlyingTypeInfo) where T : struct { throw null; }
1141+
public static System.Text.Json.Serialization.JsonConverter<T?> GetNullableConverter<T>(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; }
11191142
}
11201143
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
11211144
public sealed partial class JsonObjectInfoValues<T>
@@ -1138,10 +1161,17 @@ public JsonParameterInfoValues() { }
11381161
public System.Type ParameterType { get { throw null; } init { } }
11391162
public int Position { get { throw null; } init { } }
11401163
}
1141-
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
11421164
public abstract partial class JsonPropertyInfo
11431165
{
11441166
internal JsonPropertyInfo() { }
1167+
public System.Text.Json.Serialization.JsonConverter? CustomConverter { get { throw null; } set { } }
1168+
public System.Func<object, object?>? Get { get { throw null; } set { } }
1169+
public string Name { get { throw null; } set { } }
1170+
public System.Text.Json.Serialization.JsonNumberHandling? NumberHandling { get { throw null; } set { } }
1171+
public System.Type PropertyType { get { throw null; } }
1172+
public System.Text.Json.JsonSerializerOptions Options { get { throw null; } }
1173+
public System.Action<object, object?>? Set { get { throw null; } set { } }
1174+
public System.Func<object, object?, bool>? ShouldSerialize { get { throw null; } set { } }
11451175
}
11461176
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
11471177
public sealed partial class JsonPropertyInfoValues<T>
@@ -1162,15 +1192,40 @@ public JsonPropertyInfoValues() { }
11621192
public System.Text.Json.Serialization.Metadata.JsonTypeInfo PropertyTypeInfo { get { throw null; } init { } }
11631193
public System.Action<object, T?>? Setter { get { throw null; } init { } }
11641194
}
1165-
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
1166-
public partial class JsonTypeInfo
1195+
public static class JsonTypeInfoResolver
1196+
{
1197+
public static System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver Combine(params System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver[] resolvers) { throw null; }
1198+
}
1199+
public abstract partial class JsonTypeInfo
11671200
{
11681201
internal JsonTypeInfo() { }
1202+
public System.Text.Json.JsonSerializerOptions Options { get { throw null; } }
1203+
public System.Collections.Generic.IList<System.Text.Json.Serialization.Metadata.JsonPropertyInfo> Properties { get { throw null; } }
1204+
public System.Type Type { get { throw null; } }
1205+
public System.Text.Json.Serialization.JsonConverter Converter { get { throw null; } }
1206+
public System.Func<object>? CreateObject { get { throw null; } set { } }
1207+
public System.Text.Json.Serialization.Metadata.JsonTypeInfoKind Kind { get { throw null; } }
1208+
public System.Text.Json.Serialization.JsonNumberHandling? NumberHandling { get { throw null; } set { } }
1209+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use generic overload or System.Text.Json source generation for native AOT applications.")]
1210+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use generic overload or System.Text.Json source generation for native AOT applications.")]
1211+
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateJsonTypeInfo<T>(System.Text.Json.JsonSerializerOptions options) { throw null; }
1212+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use generic overload or System.Text.Json source generation for native AOT applications.")]
1213+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use generic overload or System.Text.Json source generation for native AOT applications.")]
1214+
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateJsonTypeInfo(System.Type type, System.Text.Json.JsonSerializerOptions options) { throw null; }
1215+
public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name) { throw null; }
11691216
}
1170-
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
11711217
public abstract partial class JsonTypeInfo<T> : System.Text.Json.Serialization.Metadata.JsonTypeInfo
11721218
{
11731219
internal JsonTypeInfo() { }
1220+
public new System.Func<T>? CreateObject { get { throw null; } set { } }
1221+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
11741222
public System.Action<System.Text.Json.Utf8JsonWriter, T>? SerializeHandler { get { throw null; } }
11751223
}
1224+
public enum JsonTypeInfoKind
1225+
{
1226+
None = 0,
1227+
Object = 1,
1228+
Enumerable = 2,
1229+
Dictionary = 3
1230+
}
11761231
}

src/libraries/System.Text.Json/src/Resources/Strings.resx

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<root>
3-
<!--
4-
Microsoft ResX Schema
5-
3+
<!--
4+
Microsoft ResX Schema
5+
66
Version 2.0
7-
8-
The primary goals of this format is to allow a simple XML format
9-
that is mostly human readable. The generation and parsing of the
10-
various data types are done through the TypeConverter classes
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
1111
associated with the data types.
12-
12+
1313
Example:
14-
14+
1515
... ado.net/XML headers & schema ...
1616
<resheader name="resmimetype">text/microsoft-resx</resheader>
1717
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
2626
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
2727
<comment>This is a comment</comment>
2828
</data>
29-
30-
There are any number of "resheader" rows that contain simple
29+
30+
There are any number of "resheader" rows that contain simple
3131
name/value pairs.
32-
33-
Each data row contains a name, and value. The row also contains a
34-
type or mimetype. Type corresponds to a .NET class that support
35-
text/value conversion through the TypeConverter architecture.
36-
Classes that don't support this are serialized and stored with the
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
3737
mimetype set.
38-
39-
The mimetype is used for serialized objects, and tells the
40-
ResXResourceReader how to depersist the object. This is currently not
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
4141
extensible. For a given mimetype the value must be set accordingly:
42-
43-
Note - application/x-microsoft.net.object.binary.base64 is the format
44-
that the ResXResourceWriter will generate, however the reader can
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
4545
read any of the formats listed below.
46-
46+
4747
mimetype: application/x-microsoft.net.object.binary.base64
48-
value : The object must be serialized with
48+
value : The object must be serialized with
4949
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
5050
: and then encoded with base64 encoding.
51-
51+
5252
mimetype: application/x-microsoft.net.object.soap.base64
53-
value : The object must be serialized with
53+
value : The object must be serialized with
5454
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
5555
: and then encoded with base64 encoding.
5656
5757
mimetype: application/x-microsoft.net.object.bytearray.base64
58-
value : The object must be serialized into a byte array
58+
value : The object must be serialized into a byte array
5959
: using a System.ComponentModel.TypeConverter
6060
: and then encoded with base64 encoding.
6161
-->
@@ -243,6 +243,15 @@
243243
<data name="JsonElementHasWrongType" xml:space="preserve">
244244
<value>The requested operation requires an element of type '{0}', but the target element has type '{1}'.</value>
245245
</data>
246+
<data name="TypeInfoResolverImmutable" xml:space="preserve">
247+
<value>Default TypeInfoResolver and custom TypeInfoResolver cannot be changed after first usage.</value>
248+
</data>
249+
<data name="TypeInfoImmutable" xml:space="preserve">
250+
<value>JsonTypeInfo cannot be changed after first usage.</value>
251+
</data>
252+
<data name="PropertyInfoImmutable" xml:space="preserve">
253+
<value>JsonPropertyInfo cannot be changed after first usage.</value>
254+
</data>
246255
<data name="MaxDepthMustBePositive" xml:space="preserve">
247256
<value>Max depth must be positive.</value>
248257
</data>
@@ -390,6 +399,12 @@
390399
<data name="SerializationConverterNotCompatible" xml:space="preserve">
391400
<value>The converter '{0}' is not compatible with the type '{1}'.</value>
392401
</data>
402+
<data name="ResolverTypeNotCompatible" xml:space="preserve">
403+
<value>TypeInfoResolver expected to return JsonTypeInfo of type '{0}' but returned JsonTypeInfo of type '{1}'.</value>
404+
</data>
405+
<data name="ResolverTypeInfoOptionsNotCompatible" xml:space="preserve">
406+
<value>TypeInfoResolver expected to return JsonTypeInfo options bound to the JsonSerializerOptions provided in the argument.</value>
407+
</data>
393408
<data name="SerializationConverterWrite" xml:space="preserve">
394409
<value>The converter '{0}' wrote too much or not enough.</value>
395410
</data>
@@ -572,13 +587,13 @@
572587
<data name="NoMetadataForType" xml:space="preserve">
573588
<value>Metadata for type '{0}' was not provided to the serializer. The serializer method used does not support reflection-based creation of serialization-related type metadata. If using source generation, ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.</value>
574589
</data>
575-
<data name="NodeCollectionIsReadOnly" xml:space="preserve">
590+
<data name="CollectionIsReadOnly" xml:space="preserve">
576591
<value>Collection is read-only.</value>
577592
</data>
578-
<data name="NodeArrayIndexNegative" xml:space="preserve">
593+
<data name="ArrayIndexNegative" xml:space="preserve">
579594
<value>Number was less than 0.</value>
580595
</data>
581-
<data name="NodeArrayTooSmall" xml:space="preserve">
596+
<data name="ArrayTooSmall" xml:space="preserve">
582597
<value>Destination array was not long enough.</value>
583598
</data>
584599
<data name="NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty" xml:space="preserve">
@@ -638,4 +653,16 @@
638653
<data name="Polymorphism_RuntimeTypeDiamondAmbiguity" xml:space="preserve">
639654
<value>Runtime type '{0}' has a diamond ambiguity between derived types '{1}' and '{2}' of polymorphic type '{3}'. Consider either removing one of the derived types or removing the 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor' setting.</value>
640655
</data>
656+
<data name="JsonTypeInfoOperationNotPossibleForKindNone" xml:space="preserve">
657+
<value>Operation is not possible when Kind is JsonTypeKind.None.</value>
658+
</data>
659+
<data name="CombineOneOfResolversIsNull" xml:space="preserve">
660+
<value>One of the provided resolvers is null.</value>
661+
</data>
662+
<data name="JsonPropertyInfoBoundToDifferentParent" xml:space="preserve">
663+
<value>JsonPropertyInfo with name '{0}' for type '{1}' is already bound to different JsonTypeInfo.</value>
664+
</data>
665+
<data name="JsonTypeInfoUsedButTypeInfoResolverNotSet" xml:space="preserve">
666+
<value>JsonTypeInfo metadata references a JsonSerializerOptions instance that doesn't specify a TypeInfoResolver.</value>
667+
</data>
641668
</root>

0 commit comments

Comments
 (0)