[release/6.0] Support JsonExtensionData in STJ source-gen (#59047)
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Tue, 14 Sep 2021 16:26:29 +0000 (10:26 -0600)
committerGitHub <noreply@github.com>
Tue, 14 Sep 2021 16:26:29 +0000 (10:26 -0600)
* Support JsonExtensionData in STJ source-gen

* Address review feedback

Co-authored-by: Layomi Akinrinade <layomia@gmail.com>
40 files changed:
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs
src/libraries/System.Text.Json/gen/Resources/Strings.resx
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf
src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs
src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs
src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs
src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Stream.cs
src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ExtensionDataTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExtensionDataTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj

index 2f9a64f..8ce170b 100644 (file)
@@ -48,7 +48,6 @@ namespace System.Text.Json.SourceGeneration
             private const string IListTypeRef = "global::System.Collections.Generic.IList";
             private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair";
             private const string ListTypeRef = "global::System.Collections.Generic.List";
-            private const string DictionaryTypeRef = "global::System.Collections.Generic.Dictionary";
             private const string JsonEncodedTextTypeRef = "global::System.Text.Json.JsonEncodedText";
             private const string JsonNamingPolicyTypeRef = "global::System.Text.Json.JsonNamingPolicy";
             private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer";
@@ -253,6 +252,12 @@ namespace {@namespace}
                                     GenerateTypeInfo(spec.TypeGenerationSpec);
                                 }
                             }
+
+                            TypeGenerationSpec? extPropTypeSpec = typeGenerationSpec.ExtensionDataPropertyTypeSpec;
+                            if (extPropTypeSpec != null)
+                            {
+                                GenerateTypeInfo(extPropTypeSpec);
+                            }
                         }
                         break;
                     case ClassType.KnownUnsupportedType:
@@ -431,9 +436,18 @@ namespace {@namespace}
                 CollectionType collectionType = typeGenerationSpec.CollectionType;
 
                 string typeRef = typeGenerationSpec.TypeRef;
-                string createObjectFuncArg = typeGenerationSpec.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor
-                    ? $"createObjectFunc: () => new {typeRef}()"
-                    : "createObjectFunc: null";
+
+                string createObjectFuncArg;
+                if (typeGenerationSpec.RuntimeTypeRef != null)
+                {
+                    createObjectFuncArg = $"createObjectFunc: () => new {typeGenerationSpec.RuntimeTypeRef}()";
+                }
+                else
+                {
+                    createObjectFuncArg = typeGenerationSpec.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor
+                        ? $"createObjectFunc: () => new {typeRef}()"
+                        : "createObjectFunc: null";
+                }
 
                 string collectionInfoCreationPrefix = collectionType switch
                 {
@@ -741,6 +755,7 @@ private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerC
         {setterNamedArg},
         {ignoreConditionNamedArg},
         hasJsonInclude: {ToCSharpKeyword(memberMetadata.HasJsonInclude)},
+        isExtensionData: {ToCSharpKeyword(memberMetadata.IsExtensionData)},
         numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)},
         propertyName: ""{clrPropertyName}"",
         {jsonPropertyNameNamedArg});
index 199c0db..2ad36dd 100644 (file)
@@ -24,8 +24,10 @@ namespace System.Text.Json.SourceGeneration
         {
             private const string SystemTextJsonNamespace = "System.Text.Json";
             private const string JsonConverterAttributeFullName = "System.Text.Json.Serialization.JsonConverterAttribute";
-            private const string JsonElementFullName = "System.Text.Json.JsonElement";
             private const string JsonConverterFactoryFullName = "System.Text.Json.Serialization.JsonConverterFactory";
+            private const string JsonElementFullName = "System.Text.Json.JsonElement";
+            private const string JsonExtensionDataAttributeFullName = "System.Text.Json.Serialization.JsonExtensionDataAttribute";
+            private const string JsonObjectFullName = "System.Text.Json.Nodes.JsonObject";
             private const string JsonIgnoreAttributeFullName = "System.Text.Json.Serialization.JsonIgnoreAttribute";
             private const string JsonIgnoreConditionFullName = "System.Text.Json.Serialization.JsonIgnoreCondition";
             private const string JsonIncludeAttributeFullName = "System.Text.Json.Serialization.JsonIncludeAttribute";
@@ -40,6 +42,8 @@ namespace System.Text.Json.SourceGeneration
             private const string TimeOnlyFullName = "System.TimeOnly";
             private const string IAsyncEnumerableFullName = "System.Collections.Generic.IAsyncEnumerable`1";
 
+            private const string DictionaryTypeRef = "global::System.Collections.Generic.Dictionary";
+
             private readonly Compilation _compilation;
             private readonly SourceProductionContext _sourceProductionContext;
             private readonly MetadataLoadContextInternal _metadataLoadContext;
@@ -77,6 +81,7 @@ namespace System.Text.Json.SourceGeneration
             private readonly Type? _uriType;
             private readonly Type? _versionType;
             private readonly Type? _jsonElementType;
+            private readonly Type? _jsonObjectType;
 
             // Unsupported types
             private readonly Type _typeType;
@@ -118,6 +123,22 @@ namespace System.Text.Json.SourceGeneration
                 defaultSeverity: DiagnosticSeverity.Error,
                 isEnabledByDefault: true);
 
+            private static DiagnosticDescriptor MultipleJsonExtensionDataAttribute { get; } = new DiagnosticDescriptor(
+                id: "SYSLIB1035",
+                title: new LocalizableResourceString(nameof(SR.MultipleJsonExtensionDataAttributeTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+                messageFormat: new LocalizableResourceString(nameof(SR.MultipleJsonExtensionDataAttributeFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+                category: JsonConstants.SystemTextJsonSourceGenerationName,
+                defaultSeverity: DiagnosticSeverity.Error,
+                isEnabledByDefault: true);
+
+            private static DiagnosticDescriptor DataExtensionPropertyInvalid { get; } = new DiagnosticDescriptor(
+                id: "SYSLIB1036",
+                title: new LocalizableResourceString(nameof(SR.DataExtensionPropertyInvalidTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+                messageFormat: new LocalizableResourceString(nameof(SR.DataExtensionPropertyInvalidFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+                category: JsonConstants.SystemTextJsonSourceGenerationName,
+                defaultSeverity: DiagnosticSeverity.Error,
+                isEnabledByDefault: true);
+
             public Parser(Compilation compilation, in SourceProductionContext sourceProductionContext)
             {
                 _compilation = compilation;
@@ -157,6 +178,7 @@ namespace System.Text.Json.SourceGeneration
                 _uriType = _metadataLoadContext.Resolve(typeof(Uri));
                 _versionType = _metadataLoadContext.Resolve(typeof(Version));
                 _jsonElementType = _metadataLoadContext.Resolve(JsonElementFullName);
+                _jsonObjectType = _metadataLoadContext.Resolve(JsonObjectFullName);
 
                 // Unsupported types.
                 _typeType = _metadataLoadContext.Resolve(typeof(Type));
@@ -588,9 +610,11 @@ namespace System.Text.Json.SourceGeneration
                 _typeGenerationSpecCache[type] = typeMetadata;
 
                 ClassType classType;
-                Type? collectionKeyType = null;
-                Type? collectionValueType = null;
+                TypeGenerationSpec? collectionKeyTypeSpec = null;
+                TypeGenerationSpec? collectionValueTypeSpec = null;
                 TypeGenerationSpec? nullableUnderlyingTypeGenSpec = null;
+                TypeGenerationSpec? dataExtensionPropGenSpec = null;
+                string? runtimeTypeRef = null;
                 List<PropertyGenerationSpec>? propGenSpecList = null;
                 ObjectConstructionStrategy constructionStrategy = default;
                 ParameterGenerationSpec[]? paramGenSpecArray = null;
@@ -653,6 +677,9 @@ namespace System.Text.Json.SourceGeneration
                     }
 
                     Type actualTypeToConvert;
+                    Type? keyType = null;
+                    Type valueType;
+                    bool needsRuntimeType = false;
 
                     if (type.IsArray)
                     {
@@ -660,13 +687,13 @@ namespace System.Text.Json.SourceGeneration
                             ? ClassType.TypeUnsupportedBySourceGen // Multi-dimentional arrays are not supported in STJ.
                             : ClassType.Enumerable;
                         collectionType = CollectionType.Array;
-                        collectionValueType = type.GetElementType();
+                        valueType = type.GetElementType();
                     }
                     else if ((actualTypeToConvert = GetCompatibleGenericBaseClass(type, _listOfTType)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.List;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = GetCompatibleGenericBaseClass(type, _dictionaryType)) != null)
                     {
@@ -674,8 +701,8 @@ namespace System.Text.Json.SourceGeneration
                         collectionType = CollectionType.Dictionary;
 
                         Type[] genericArgs = actualTypeToConvert.GetGenericArguments();
-                        collectionKeyType = genericArgs[0];
-                        collectionValueType = genericArgs[1];
+                        keyType = genericArgs[0];
+                        valueType = genericArgs[1];
                     }
                     else if (type.IsImmutableDictionaryType(sourceGenType: true))
                     {
@@ -683,8 +710,8 @@ namespace System.Text.Json.SourceGeneration
                         collectionType = CollectionType.ImmutableDictionary;
 
                         Type[] genericArgs = type.GetGenericArguments();
-                        collectionKeyType = genericArgs[0];
-                        collectionValueType = genericArgs[1];
+                        keyType = genericArgs[0];
+                        valueType = genericArgs[1];
                     }
                     else if ((actualTypeToConvert = type.GetCompatibleGenericInterface(_idictionaryOfTKeyTValueType)) != null)
                     {
@@ -692,8 +719,10 @@ namespace System.Text.Json.SourceGeneration
                         collectionType = CollectionType.IDictionaryOfTKeyTValue;
 
                         Type[] genericArgs = actualTypeToConvert.GetGenericArguments();
-                        collectionKeyType = genericArgs[0];
-                        collectionValueType = genericArgs[1];
+                        keyType = genericArgs[0];
+                        valueType = genericArgs[1];
+
+                        needsRuntimeType = type == actualTypeToConvert;
                     }
                     else if ((actualTypeToConvert = type.GetCompatibleGenericInterface(_ireadonlyDictionaryType)) != null)
                     {
@@ -701,93 +730,109 @@ namespace System.Text.Json.SourceGeneration
                         collectionType = CollectionType.IReadOnlyDictionary;
 
                         Type[] genericArgs = actualTypeToConvert.GetGenericArguments();
-                        collectionKeyType = genericArgs[0];
-                        collectionValueType = genericArgs[1];
+                        keyType = genericArgs[0];
+                        valueType = genericArgs[1];
+
+                        needsRuntimeType = type == actualTypeToConvert;
                     }
                     else if (type.IsImmutableEnumerableType(sourceGenType: true))
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.ImmutableEnumerable;
-                        collectionValueType = type.GetGenericArguments()[0];
+                        valueType = type.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = type.GetCompatibleGenericInterface(_ilistOfTType)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.IListOfT;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = type.GetCompatibleGenericInterface(_isetType)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.ISet;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = type.GetCompatibleGenericInterface(_icollectionOfTType)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.ICollectionOfT;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = GetCompatibleGenericBaseClass(type, _stackOfTType)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.StackOfT;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = GetCompatibleGenericBaseClass(type, _queueOfTType)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.QueueOfT;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = type.GetCompatibleGenericBaseClass(_concurrentStackType, _objectType, sourceGenType: true)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.ConcurrentStack;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = type.GetCompatibleGenericBaseClass(_concurrentQueueType, _objectType, sourceGenType: true)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.ConcurrentQueue;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if ((actualTypeToConvert = type.GetCompatibleGenericInterface(_ienumerableOfTType)) != null)
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.IEnumerableOfT;
-                        collectionValueType = actualTypeToConvert.GetGenericArguments()[0];
+                        valueType = actualTypeToConvert.GetGenericArguments()[0];
                     }
                     else if (_idictionaryType.IsAssignableFrom(type))
                     {
                         classType = ClassType.Dictionary;
                         collectionType = CollectionType.IDictionary;
-                        collectionKeyType = _stringType;
-                        collectionValueType = _objectType;
+                        keyType = _stringType;
+                        valueType = _objectType;
+
+                        needsRuntimeType = type == actualTypeToConvert;
                     }
                     else if (_ilistType.IsAssignableFrom(type))
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.IList;
-                        collectionValueType = _objectType;
+                        valueType = _objectType;
                     }
                     else if (_stackType.IsAssignableFrom(type))
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.Stack;
-                        collectionValueType = _objectType;
+                        valueType = _objectType;
                     }
                     else if (_queueType.IsAssignableFrom(type))
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.Queue;
-                        collectionValueType = _objectType;
+                        valueType = _objectType;
                     }
                     else
                     {
                         classType = ClassType.Enumerable;
                         collectionType = CollectionType.IEnumerable;
-                        collectionValueType = _objectType;
+                        valueType = _objectType;
+                    }
+
+                    collectionValueTypeSpec = GetOrAddTypeGenerationSpec(valueType, generationMode);
+
+                    if (keyType != null)
+                    {
+                        collectionKeyTypeSpec = GetOrAddTypeGenerationSpec(keyType, generationMode);
+
+                        if (needsRuntimeType)
+                        {
+                            runtimeTypeRef = GetDictionaryTypeRef(collectionKeyTypeSpec, collectionValueTypeSpec);
+                        }
                     }
                 }
                 else if (_knownUnsupportedTypes.Contains(type) ||
@@ -892,6 +937,23 @@ namespace System.Text.Json.SourceGeneration
 
                                 // The property type may be implicitly in the context, so add that as well.
                                 hasTypeFactoryConverter |= spec.TypeGenerationSpec.HasTypeFactoryConverter;
+
+                                if (spec.IsExtensionData)
+                                {
+                                    if (dataExtensionPropGenSpec != null)
+                                    {
+                                        _sourceProductionContext.ReportDiagnostic(Diagnostic.Create(MultipleJsonExtensionDataAttribute, Location.None, new string[] { type.Name }));
+                                    }
+
+                                    Type propType = spec.TypeGenerationSpec.Type;
+                                    if (!IsValidDataExtensionPropertyType(propType))
+                                    {
+                                        _sourceProductionContext.ReportDiagnostic(Diagnostic.Create(DataExtensionPropertyInvalid, Location.None, new string[] { type.Name, spec.ClrName }));
+                                    }
+
+                                    dataExtensionPropGenSpec = GetOrAddTypeGenerationSpec(propType, generationMode);
+                                    _implicitlyRegisteredTypes.Add(dataExtensionPropGenSpec);
+                                }
                             }
                         }
 
@@ -910,10 +972,12 @@ namespace System.Text.Json.SourceGeneration
                     propGenSpecList,
                     paramGenSpecArray,
                     collectionType,
-                    collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeGenerationSpec(collectionKeyType, generationMode) : null,
-                    collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType, generationMode) : null,
+                    collectionKeyTypeSpec,
+                    collectionValueTypeSpec,
                     constructionStrategy,
                     nullableUnderlyingTypeMetadata: nullableUnderlyingTypeGenSpec,
+                    runtimeTypeRef,
+                    dataExtensionPropGenSpec,
                     converterInstatiationLogic,
                     implementsIJsonOnSerialized : implementsIJsonOnSerialized,
                     implementsIJsonOnSerializing : implementsIJsonOnSerializing,
@@ -933,6 +997,26 @@ namespace System.Text.Json.SourceGeneration
                 return type.GetCompatibleGenericInterface(_iAsyncEnumerableGenericType) is not null;
             }
 
+            private static string GetDictionaryTypeRef(TypeGenerationSpec keyType, TypeGenerationSpec valueType)
+                => $"{DictionaryTypeRef}<{keyType.TypeRef}, {valueType.TypeRef}>";
+
+            private bool IsValidDataExtensionPropertyType(Type type)
+            {
+                if (type == _jsonObjectType)
+                {
+                    return true;
+                }
+
+                Type? actualDictionaryType  = type.GetCompatibleGenericInterface(_idictionaryOfTKeyTValueType);
+                if (actualDictionaryType == null)
+                {
+                    return false;
+                }
+
+                Type[] genericArguments = actualDictionaryType.GetGenericArguments();
+                return genericArguments[0] == _stringType && (genericArguments[1] == _objectType || genericArguments[1] == _jsonElementType);
+            }
+
             private Type GetCompatibleGenericBaseClass(Type type, Type baseType)
                 => type.GetCompatibleGenericBaseClass(baseType, _objectType);
 
@@ -966,12 +1050,15 @@ namespace System.Text.Json.SourceGeneration
                     ignoredMember.IsVirtual;
             }
 
-            private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, bool isVirtual, JsonSourceGenerationMode generationMode)
+            private PropertyGenerationSpec GetPropertyGenerationSpec(
+                MemberInfo memberInfo,
+                bool isVirtual,
+                JsonSourceGenerationMode generationMode)
             {
                 Type memberCLRType = GetMemberClrType(memberInfo);
                 IList<CustomAttributeData> attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo);
 
-                ProcessCustomAttributes(
+                ProcessMemberCustomAttributes(
                     attributeDataList,
                     memberCLRType,
                     out bool hasJsonInclude,
@@ -980,7 +1067,8 @@ namespace System.Text.Json.SourceGeneration
                     out JsonNumberHandling? numberHandling,
                     out string? converterInstantiationLogic,
                     out int order,
-                    out bool hasFactoryConverter);
+                    out bool hasFactoryConverter,
+                    out bool isExtensionData);
 
                 ProcessMember(
                     memberInfo,
@@ -1015,6 +1103,7 @@ namespace System.Text.Json.SourceGeneration
                     NumberHandling = numberHandling,
                     Order = order,
                     HasJsonInclude = hasJsonInclude,
+                    IsExtensionData = isExtensionData,
                     TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode),
                     DeclaringTypeRef = memberInfo.DeclaringType.GetCompilableName(),
                     ConverterInstantiationLogic = converterInstantiationLogic,
@@ -1037,7 +1126,7 @@ namespace System.Text.Json.SourceGeneration
                 throw new InvalidOperationException();
             }
 
-            private void ProcessCustomAttributes(
+            private void ProcessMemberCustomAttributes(
                 IList<CustomAttributeData> attributeDataList,
                 Type memberCLRType,
                 out bool hasJsonInclude,
@@ -1046,7 +1135,8 @@ namespace System.Text.Json.SourceGeneration
                 out JsonNumberHandling? numberHandling,
                 out string? converterInstantiationLogic,
                 out int order,
-                out bool hasFactoryConverter)
+                out bool hasFactoryConverter,
+                out bool isExtensionData)
             {
                 hasJsonInclude = false;
                 jsonPropertyName = null;
@@ -1054,6 +1144,7 @@ namespace System.Text.Json.SourceGeneration
                 numberHandling = default;
                 converterInstantiationLogic = null;
                 order = 0;
+                isExtensionData = false;
 
                 bool foundDesignTimeCustomConverter = false;
                 hasFactoryConverter = false;
@@ -1115,6 +1206,11 @@ namespace System.Text.Json.SourceGeneration
                                     order = (int)ctorArgs[0].Value;
                                 }
                                 break;
+                            case JsonExtensionDataAttributeFullName:
+                                {
+                                    isExtensionData = true;
+                                }
+                                break;
                             default:
                                 break;
                         }
@@ -1315,30 +1411,33 @@ namespace System.Text.Json.SourceGeneration
 
                 _knownTypes.UnionWith(_numberTypes);
                 _knownTypes.Add(_booleanType);
-                _knownTypes.Add(_byteArrayType);
                 _knownTypes.Add(_charType);
                 _knownTypes.Add(_dateTimeType);
-                _knownTypes.Add(_dateTimeOffsetType);
-                _knownTypes.Add(_guidType);
                 _knownTypes.Add(_objectType);
                 _knownTypes.Add(_stringType);
-                _knownTypes.Add(_uriType);
-                _knownTypes.Add(_versionType);
-                _knownTypes.Add(_jsonElementType);
+
+                AddTypeIfNotNull(_knownTypes, _byteArrayType);
+                AddTypeIfNotNull(_knownTypes, _dateTimeOffsetType);
+                AddTypeIfNotNull(_knownTypes, _guidType);
+                AddTypeIfNotNull(_knownTypes, _uriType);
+                AddTypeIfNotNull(_knownTypes, _versionType);
+                AddTypeIfNotNull(_knownTypes, _jsonElementType);
+                AddTypeIfNotNull(_knownTypes, _jsonObjectType);
 
                 _knownUnsupportedTypes.Add(_typeType);
                 _knownUnsupportedTypes.Add(_serializationInfoType);
                 _knownUnsupportedTypes.Add(_intPtrType);
                 _knownUnsupportedTypes.Add(_uIntPtrType);
 
-                if (_dateOnlyType != null)
-                {
-                    _knownUnsupportedTypes.Add(_dateOnlyType);
-                }
+                AddTypeIfNotNull(_knownUnsupportedTypes, _dateOnlyType);
+                AddTypeIfNotNull(_knownUnsupportedTypes, _timeOnlyType);
 
-                if (_timeOnlyType != null)
+                static void AddTypeIfNotNull(HashSet<Type> types, Type? type)
                 {
-                    _knownUnsupportedTypes.Add(_timeOnlyType);
+                    if (type != null)
+                    {
+                        types.Add(type);
+                    }
                 }
             }
         }
index 886381a..5040045 100644 (file)
@@ -76,6 +76,11 @@ namespace System.Text.Json.SourceGeneration
         public bool HasJsonInclude { get; init; }
 
         /// <summary>
+        /// Whether the property has the JsonExtensionDataAttribute.
+        /// </summary>
+        public bool IsExtensionData { get; init; }
+
+        /// <summary>
         /// Generation specification for the property's type.
         /// </summary>
         public TypeGenerationSpec TypeGenerationSpec { get; init; }
index 5e60f5e..389b14e 100644 (file)
   <data name="MultipleJsonConstructorAttributeTitle" xml:space="preserve">
     <value>Type has multiple constructors annotated with JsonConstructorAttribute.</value>
   </data>
+  <data name="MultipleJsonExtensionDataAttributeFormat" xml:space="preserve">
+    <value>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</value>
+  </data>
+  <data name="MultipleJsonExtensionDataAttributeTitle" xml:space="preserve">
+    <value>Type has multiple members annotated with JsonExtensionDataAttribute.</value>
+  </data>
+  <data name="DataExtensionPropertyInvalidFormat" xml:space="preserve">
+    <value>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</value>
+  </data>
+  <data name="DataExtensionPropertyInvalidTitle" xml:space="preserve">
+    <value>Data extension property type invalid.</value>
+  </data>
 </root>
\ No newline at end of file
index 4bde155..710c988 100644 (file)
         <target state="translated">Odvozené typy JsonSerializerContext a všechny obsahující typy musí být částečné.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">Existuje několik typů s názvem {0}. Zdroj se vygeneroval pro první zjištěný typ. Tuto kolizi vyřešíte pomocí JsonSerializableAttribute.TypeInfoPropertyName.</target>
         <target state="translated">Typ obsahuje více konstruktorů anotovaných s JsonConstructorAttribute.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">Nevygenerovala se metadata serializace pro typ {0}.</target>
index 093d8a6..e4c65b6 100644 (file)
         <target state="translated">Abgeleitete JsonSerializerContext-Typen und alle enthaltenden Typen müssen partiell sein.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">Es sind mehrere Typen namens "{0}" vorhanden. Die Quelle wurde für den ersten festgestellten Typ generiert. Verwenden Sie "JsonSerializableAttribute.TypeInfoPropertyName", um diesen Konflikt zu beheben.</target>
         <target state="translated">Der Typ weist mehrere Konstruktoren mit dem Kommentar JsonConstructorAttribute auf.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">Die Serialisierungsmetadaten für den Typ "{0}" wurden nicht generiert.</target>
index ed12d99..e97f578 100644 (file)
         <target state="translated">Los tipos derivados "JsonSerializerContext" y todos los tipos que contienen deben ser parciales.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">Hay varios tipos denominados {0}. El origen se generó para el primero detectado. Use "JsonSerializableAttribute.TypeInfoPropertyName" para resolver esta colisión.</target>
         <target state="translated">El tipo tiene varios constructores anotados con JsonConstructorAttribute.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">No generó metadatos de serialización para el tipo '{0}".</target>
index ddea910..d4daa7c 100644 (file)
         <target state="translated">Les types dérivés 'JsonSerializerContext' et tous les types conteneurs doivent être partiels.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">Plusieurs types nommés {0}. La source a été générée pour la première détection détectée. Utilisez « JsonSerializableAttribute.TypeInfoPropertyName » pour résoudre cette collision.</target>
         <target state="translated">Le type a plusieurs constructeurs annotés avec JsonConstructorAttribute.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">Les métadonnées de sérialisation pour le type « {0} » n’ont pas été générées.</target>
index d26aba1..b72f656 100644 (file)
         <target state="translated">I tipi derivati 'JsonSerializerContext' e tutti i tipi contenenti devono essere parziali.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">Sono presenti più tipi denominati {0}. L'origine è stata generata per il primo tipo rilevato. Per risolvere questa collisione, usare 'JsonSerializableAttribute.TypeInfoPropertyName'.</target>
         <target state="translated">Il tipo contiene più costruttori che presentano l'annotazione JsonConstructorAttribute.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">Non sono stati generati metadati di serializzazione per il tipo '{0}'.</target>
index 50270d6..17f511d 100644 (file)
         <target state="translated">派生した 'JsonSerializerContext' 型と含まれているすべての型は部分的である必要があります。</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">{0} と名前が付けられた種類が複数あります。最初に検出されたものに対してソースが生成されました。この問題を解決するには、'JsonSerializableAttribute.TypeInfoPropertyName' を使用します。</target>
         <target state="translated">型には、JsonConstructorAttribute で注釈が付けられた複数のコンストラクターがあります。</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">'{0}'型 のシリアル化メタデータを生成ませんでした。</target>
index a1fd856..1a45826 100644 (file)
         <target state="translated">파생된 'JsonSerializerContext' 형식과 포함하는 모든 형식은 부분이어야 합니다.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">이름이 {0}인 형식이 여러 개 있습니다. 처음 검색한 원본에 대해 원본이 생성되었습니다. 이 충돌을 해결하려면 'JsonSerializableAttribute.TypeInfoPropertyName'을 사용하세요.</target>
         <target state="translated">해당 형식에 JsonConstructorAttribute로 주석이 추가된 여러 생성자가 있습니다.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">'{0}' 형식에 대한 직렬화 메타데이터가 생성되지 않았습니다.</target>
index e04db80..e757ef4 100644 (file)
         <target state="translated">Pochodne typy "JsonSerializerContext" i wszystkich zawierające typy muszą być częściowe.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">Istnieje wiele typów o nazwie {0}. Wygenerowano źródło dla pierwszego wykrytego elementu. Aby rozwiązać tę kolizję, użyj „JsonSerializableAttribute. TypeInfoPropertyName”.</target>
         <target state="translated">Typ ma wiele konstruktorów z adnotacją JsonConstructorAttribute.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">Nie wygenerowano metadanych serializacji dla typu „{0}”.</target>
index bd08c88..34765df 100644 (file)
         <target state="translated">Os tipos derivados de 'JsonSerializerContext' e todos os tipos contidos devem ser parciais.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">Existem vários tipos chamados {0}. A fonte foi gerada para o primeiro detectado. Use 'JsonSerializableAttribute.TypeInfoPropertyName' para resolver esta colisão.</target>
         <target state="translated">O tipo tem vários construtores anotados com JsonConstructorAttribute.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">Não gerou metadados de serialização para o tipo '{0}'.</target>
index fdd7824..bdac6af 100644 (file)
         <target state="translated">Производные типы "JsonSerializerContext" и все содержащие типы должны быть частичными.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">Существует несколько типов с именем {0}. Исходный код сформирован для первого обнаруженного типа. Используйте JsonSerializableAttribute.TypeInfoPropertyName для устранения этого конфликта.</target>
         <target state="translated">Тип имеет несколько конструкторов, аннотированных с использованием JsonConstructorAttribute.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">Метаданные сериализации для типа "{0}" не сформированы.</target>
index 7844892..e8eedfa 100644 (file)
         <target state="translated">Türetilmiş 'JsonSerializerContext' türleri ve bunları içeren tüm türler kısmi olmalıdır.</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">{0} adını taşıyan birden çok tür var. Kaynak, algılanan ilk tür için oluşturuldu. Bu çarpışmayı çözmek için 'JsonSerializableAttribute.TypeInfoPropertyName' özelliğini kullanın.</target>
         <target state="translated">Türün JsonConstructorAttribute ile açıklanan birden çok oluşturucusu var.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">'{0}' türü için serileştirme meta verileri oluşturulmadı.</target>
index 10e68c8..12bee67 100644 (file)
         <target state="translated">派生的 “JsonSerializerContext” 类型以及所有包含类型必须是部分。</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">有多个名为 {0} 的类型。已为第一个检测到类型的生成源。请使用 'JsonSerializableAttribute.TypeInfoPropertyName' 以解决此冲突。</target>
         <target state="translated">类型具有用 JsonConstructorAttribute 批注的多个构造函数。</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">未生成类型 '{0}' 的序列化元数据。</target>
index 2b5a599..0f1c6dd 100644 (file)
         <target state="translated">衍生的 'JsonSerializerContext' 類型和所有包含類型必須是部份。</target>
         <note />
       </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidFormat">
+        <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</source>
+        <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DataExtensionPropertyInvalidTitle">
+        <source>Data extension property type invalid.</source>
+        <target state="new">Data extension property type invalid.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="translated">有多個名為 {0} 的類型。已為偵測到的第一個項目產生來源。請使用 'JsonSerializableAttribute.TypeInfoPropertyName' 解決此衝突。</target>
         <target state="translated">類型包含多個以 JsonConstructorAttribute 註解的建構函式。</target>
         <note />
       </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+        <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+        <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+        <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+        <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="TypeNotSupportedMessageFormat">
         <source>Did not generate serialization metadata for type '{0}'.</source>
         <target state="translated">未產生類型 '{0}' 的序列化中繼資料。</target>
index be51f94..819f952 100644 (file)
@@ -58,6 +58,14 @@ namespace System.Text.Json.SourceGeneration
 
         public TypeGenerationSpec? NullableUnderlyingTypeMetadata { get; private set; }
 
+        /// <summary>
+        /// Supports deserialization of extension data dictionaries typed as I[ReadOnly]Dictionary<string, object/JsonElement>.
+        /// Specifies a concrete type to instanciate, which would be Dictionary<string, object/JsonElement>.
+        /// </summary>
+        public string? RuntimeTypeRef { get; private set; }
+
+        public TypeGenerationSpec? ExtensionDataPropertyTypeSpec {  get; private set; }
+
         public string? ConverterInstantiationLogic { get; private set; }
 
         // Only generate certain helper methods if necessary.
@@ -109,6 +117,8 @@ namespace System.Text.Json.SourceGeneration
             TypeGenerationSpec? collectionValueTypeMetadata,
             ObjectConstructionStrategy constructionStrategy,
             TypeGenerationSpec? nullableUnderlyingTypeMetadata,
+            string? runtimeTypeRef,
+            TypeGenerationSpec? extensionDataPropertyTypeSpec,
             string? converterInstantiationLogic,
             bool implementsIJsonOnSerialized,
             bool implementsIJsonOnSerializing,
@@ -130,6 +140,8 @@ namespace System.Text.Json.SourceGeneration
             CollectionValueTypeMetadata = collectionValueTypeMetadata;
             ConstructionStrategy = constructionStrategy;
             NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata;
+            RuntimeTypeRef = runtimeTypeRef;
+            ExtensionDataPropertyTypeSpec = extensionDataPropertyTypeSpec;
             ConverterInstantiationLogic = converterInstantiationLogic;
             ImplementsIJsonOnSerialized = implementsIJsonOnSerialized;
             ImplementsIJsonOnSerializing = implementsIJsonOnSerializing;
@@ -221,6 +233,11 @@ ReturnFalse:
         {
             if (ClassType == ClassType.Object)
             {
+                if (ExtensionDataPropertyTypeSpec != null)
+                {
+                    return false;
+                }
+
                 foreach (PropertyGenerationSpec property in PropertyGenSpecList)
                 {
                     if (property.TypeGenerationSpec.Type.IsObjectType() ||
index 09340a2..23b6322 100644 (file)
@@ -963,6 +963,7 @@ namespace System.Text.Json.Serialization.Metadata
         public static System.Text.Json.Serialization.JsonConverter<int> Int32Converter { get { throw null; } }
         public static System.Text.Json.Serialization.JsonConverter<long> Int64Converter { get { throw null; } }
         public static System.Text.Json.Serialization.JsonConverter<System.Text.Json.JsonElement> JsonElementConverter { get { throw null; } }
+        public static System.Text.Json.Serialization.JsonConverter<System.Text.Json.Nodes.JsonObject> JsonObjectConverter { get { throw null; } }
         public static System.Text.Json.Serialization.JsonConverter<object> ObjectConverter { get { throw null; } }
         [System.CLSCompliantAttribute(false)]
         public static System.Text.Json.Serialization.JsonConverter<sbyte> SByteConverter { get { throw null; } }
@@ -994,7 +995,7 @@ namespace System.Text.Json.Serialization.Metadata
         public static JsonTypeInfo<TCollection> CreateISetInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.ISet<TElement> { throw null; }
         public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.List<TElement> { throw null; }
         public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateObjectInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<T> objectInfo) where T : notnull { throw null; }
-        public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo<T>(System.Text.Json.JsonSerializerOptions options, bool isProperty, bool isPublic, bool isVirtual, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter<T>? converter, System.Func<object, T?>? getter, System.Action<object, T?>? setter, System.Text.Json.Serialization.JsonIgnoreCondition? ignoreCondition, bool hasJsonInclude, System.Text.Json.Serialization.JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) { throw null; }
+        public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo<T>(System.Text.Json.JsonSerializerOptions options, bool isProperty, bool isPublic, bool isVirtual, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter<T>? converter, System.Func<object, T?>? getter, System.Action<object, T?>? setter, System.Text.Json.Serialization.JsonIgnoreCondition? ignoreCondition, bool hasJsonInclude, bool isExtensionData, System.Text.Json.Serialization.JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) { throw null; }
         public static JsonTypeInfo<TCollection> CreateQueueInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.Queue<TElement> { throw null; }
         public static JsonTypeInfo<TCollection> CreateStackInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.Stack<TElement> { throw null; }
         public static JsonTypeInfo<TCollection> CreateStackOrQueueInfo<TCollection>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc, System.Action<TCollection, object?> addFunc) where TCollection : System.Collections.IEnumerable { throw null; }
index e1cb127..6485157 100644 (file)
     <value>The JSON property name for '{0}.{1}' cannot be null.</value>
   </data>
   <data name="SerializationDataExtensionPropertyInvalid" xml:space="preserve">
-    <value>The data extension property '{0}.{1}' does not match the required signature of 'IDictionary&lt;string, JsonElement&gt;', 'IDictionary&lt;string, object&gt;' or 'JsonObject'.</value>
+    <value>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</value>
   </data>
   <data name="SerializationDuplicateTypeAttribute" xml:space="preserve">
     <value>The type '{0}' cannot have more than one member that has the attribute '{1}'.</value>
index 88b93f9..9bc2f3b 100644 (file)
@@ -22,7 +22,7 @@ namespace System.Text.Json.Serialization.Converters
         private JsonConverter<T>? _converter;
 
         // A backing converter for when fast-path logic cannot be used.
-        private JsonConverter<T> Converter
+        internal JsonConverter<T> Converter
         {
             get
             {
index 497c380..3deefaf 100644 (file)
@@ -4,6 +4,7 @@
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization.Converters;
 using System.Text.Json.Serialization.Metadata;
 
 namespace System.Text.Json.Serialization
@@ -498,7 +499,10 @@ namespace System.Text.Json.Serialization
                 return TryWrite(writer, value, options, ref state);
             }
 
-            if (this is not JsonDictionaryConverter<T> dictionaryConverter)
+            JsonDictionaryConverter<T>? dictionaryConverter = this as JsonDictionaryConverter<T>
+                ?? (this as JsonMetadataServicesConverter<T>)?.Converter as JsonDictionaryConverter<T>;
+
+            if (dictionaryConverter == null)
             {
                 // If not JsonDictionaryConverter<T> then we are JsonObject.
                 // Avoid a type reference to JsonObject and its converter to support trimming.
index 2f3b8e7..20d5b1f 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.Text.Json.Nodes;
 using System.Text.Json.Serialization.Converters;
 
 namespace System.Text.Json.Serialization.Metadata
@@ -87,6 +88,12 @@ namespace System.Text.Json.Serialization.Metadata
         private static JsonConverter<JsonElement>? s_jsonElementConverter;
 
         /// <summary>
+        /// Returns a <see cref="JsonConverter{T}"/> instance that converts <see cref="JsonObject"/> values.
+        /// </summary>
+        public static JsonConverter<JsonObject> JsonObjectConverter => s_jsonObjectConverter ??= new JsonObjectConverter();
+        private static JsonConverter<JsonObject>? s_jsonObjectConverter;
+
+        /// <summary>
         /// Returns a <see cref="JsonConverter{T}"/> instance that converts <see cref="object"/> values.
         /// </summary>
         public static JsonConverter<object?> ObjectConverter => s_objectConverter ??= new ObjectConverter();
index 20b33ac..f7cd608 100644 (file)
@@ -1,7 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Collections.Generic;
 using System.ComponentModel;
 
 namespace System.Text.Json.Serialization.Metadata
@@ -28,6 +27,7 @@ namespace System.Text.Json.Serialization.Metadata
         /// <param name="ignoreCondition">Specifies a condition for the property to be ignored.</param>
         /// <param name="numberHandling">If the property or field is a number, specifies how it should processed when serializing and deserializing.</param>
         /// <param name="hasJsonInclude">Whether the property was annotated with <see cref="JsonIncludeAttribute"/>.</param>
+        /// <param name="isExtensionData">Whether the property was annotated with <see cref="JsonExtensionDataAttribute"/>.</param>
         /// <param name="propertyName">The CLR name of the property or field.</param>
         /// <param name="jsonPropertyName">The name to be used when processing the property or field, specified by <see cref="JsonPropertyNameAttribute"/>.</param>
         /// <returns>A <see cref="JsonPropertyInfo"/> instance intialized with the provided metadata.</returns>
@@ -43,6 +43,7 @@ namespace System.Text.Json.Serialization.Metadata
             Action<object, T?>? setter,
             JsonIgnoreCondition? ignoreCondition,
             bool hasJsonInclude,
+            bool isExtensionData,
             JsonNumberHandling? numberHandling,
             string propertyName,
             string? jsonPropertyName)
@@ -93,6 +94,7 @@ namespace System.Text.Json.Serialization.Metadata
                 setter,
                 ignoreCondition,
                 hasJsonInclude,
+                isExtensionData,
                 numberHandling,
                 propertyName,
                 jsonPropertyName);
index 0ae3d81..3b8c003 100644 (file)
@@ -504,6 +504,11 @@ namespace System.Text.Json.Serialization.Metadata
         internal bool SrcGen_HasJsonInclude { get; set; }
 
         /// <summary>
+        /// Relevant to source generated metadata: did the property have the <see cref="JsonExtensionDataAttribute"/>?
+        /// </summary>
+        internal bool SrcGen_IsExtensionData { get; set; }
+
+        /// <summary>
         /// Relevant to source generated metadata: is the property public?
         /// </summary>
         internal bool SrcGen_IsPublic { get; set; }
index 6111f46..71363ca 100644 (file)
@@ -127,6 +127,7 @@ namespace System.Text.Json.Serialization.Metadata
             Action<object, T?>? setter,
             JsonIgnoreCondition? ignoreCondition,
             bool hasJsonInclude,
+            bool isExtensionData,
             JsonNumberHandling? numberHandling,
             string propertyName,
             string? jsonPropertyName)
@@ -157,6 +158,7 @@ namespace System.Text.Json.Serialization.Metadata
 
             SrcGen_IsPublic = isPublic;
             SrcGen_HasJsonInclude = hasJsonInclude;
+            SrcGen_IsExtensionData = isExtensionData;
             DeclaredPropertyType = typeof(T);
             ConverterBase = converter;
 
index 00911d7..d3f2c3c 100644 (file)
@@ -613,6 +613,16 @@ namespace System.Text.Json.Serialization.Metadata
                     continue;
                 }
 
+                if (jsonPropertyInfo.SrcGen_IsExtensionData)
+                {
+                    // Source generator compile-time type inspection has performed this validation for us.
+                    Debug.Assert(DataExtensionProperty == null);
+                    Debug.Assert(IsValidDataExtensionProperty(jsonPropertyInfo));
+
+                    DataExtensionProperty = jsonPropertyInfo;
+                    continue;
+                }
+
                 CacheMember(jsonPropertyInfo, propertyCache, ref ignoredMembers);
             }
 
index 28fb276..cafba2f 100644 (file)
@@ -504,7 +504,7 @@ namespace System.Text.Json.Serialization.Metadata
                 else if (DataExtensionProperty != null &&
                     StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, DataExtensionProperty.NameAsString))
                 {
-                    ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty.MemberInfo!, Type);
+                    ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty);
                 }
             }
 
@@ -554,17 +554,7 @@ namespace System.Text.Json.Serialization.Metadata
 
         private void ValidateAndAssignDataExtensionProperty(JsonPropertyInfo jsonPropertyInfo)
         {
-            Type memberType = jsonPropertyInfo.DeclaredPropertyType;
-            JsonConverter? converter = null;
-            if (typeof(IDictionary<string, object>).IsAssignableFrom(memberType) ||
-                typeof(IDictionary<string, JsonElement>).IsAssignableFrom(memberType) ||
-                // Avoid a reference to typeof(JsonNode) to support trimming.
-                (memberType.FullName == JsonObjectTypeName && ReferenceEquals(memberType.Assembly, GetType().Assembly)))
-            {
-                converter = Options.GetConverterInternal(memberType);
-            }
-
-            if (converter == null)
+            if (!IsValidDataExtensionProperty(jsonPropertyInfo))
             {
                 ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(Type, jsonPropertyInfo);
             }
@@ -572,6 +562,18 @@ namespace System.Text.Json.Serialization.Metadata
             DataExtensionProperty = jsonPropertyInfo;
         }
 
+        private bool IsValidDataExtensionProperty(JsonPropertyInfo jsonPropertyInfo)
+        {
+            Type memberType = jsonPropertyInfo.DeclaredPropertyType;
+
+            bool typeIsValid = typeof(IDictionary<string, object>).IsAssignableFrom(memberType) ||
+                typeof(IDictionary<string, JsonElement>).IsAssignableFrom(memberType) ||
+                // Avoid a reference to typeof(JsonNode) to support trimming.
+                (memberType.FullName == JsonObjectTypeName && ReferenceEquals(memberType.Assembly, GetType().Assembly));
+
+            return typeIsValid && Options.GetConverterInternal(memberType) != null;
+        }
+
         private static JsonParameterInfo CreateConstructorParameter(
             JsonParameterInfoValues parameterInfo,
             JsonPropertyInfo jsonPropertyInfo,
index 9de23e8..a75f5ea 100644 (file)
@@ -215,9 +215,9 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(MemberInfo memberInfo, Type classType)
+        public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(JsonPropertyInfo jsonPropertyInfo)
         {
-            throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, memberInfo, classType));
+            throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, jsonPropertyInfo.ClrName, jsonPropertyInfo.DeclaringType));
         }
 
         [DoesNotReturn]
index e2275bc..63c5398 100644 (file)
@@ -11,9 +11,6 @@ namespace System.Text.Json.Serialization.Tests
     {
         [Fact]
         [OuterLoop]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task MultipleThreadsLooping()
         {
             const int Iterations = 100;
@@ -25,13 +22,10 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task MultipleThreads()
         {
             // Verify the test class has >32 properties since that is a threshold for using the fallback dictionary.
-            Assert.True(typeof(ClassWithConstructor_SimpleAndComplexParameters).GetProperties(BindingFlags.Instance | BindingFlags.Public).Length > 32);
+            Assert.True(typeof(ObjWCtorMixedParams).GetProperties(BindingFlags.Instance | BindingFlags.Public).Length > 32);
 
             async Task DeserializeObjectAsync(string json, Type type, JsonSerializerOptions options)
             {
@@ -60,7 +54,7 @@ namespace System.Text.Json.Serialization.Tests
 
             async Task SerializeObject(Type type, JsonSerializerOptions options)
             {
-                var obj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+                var obj = ObjWCtorMixedParams.GetInstance();
                 await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
             };
 
@@ -87,7 +81,7 @@ namespace System.Text.Json.Serialization.Tests
                 await Task.WhenAll(tasks);
             }
 
-            await RunTestAsync(typeof(ClassWithConstructor_SimpleAndComplexParameters));
+            await RunTestAsync(typeof(ObjWCtorMixedParams));
             await RunTestAsync(typeof(Person_Class));
             await RunTestAsync(typeof(Parameterized_Class_With_ComplexTuple));
         }
@@ -99,13 +93,13 @@ namespace System.Text.Json.Serialization.Tests
             var options = new JsonSerializerOptions();
 
             string json = "{}";
-            await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+            await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(json, options);
 
-            ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+            ObjWCtorMixedParams testObj = ObjWCtorMixedParams.GetInstance();
             testObj.Verify();
 
             json = await JsonSerializerWrapperForString.SerializeWrapper(testObj, options);
-            testObj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+            testObj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(json, options);
             testObj.Verify();
         }
 
@@ -115,15 +109,15 @@ namespace System.Text.Json.Serialization.Tests
             // Use local options to avoid obtaining already cached metadata from the default options.
             var options = new JsonSerializerOptions();
 
-            ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+            ObjWCtorMixedParams testObj = ObjWCtorMixedParams.GetInstance();
             testObj.Verify();
 
             string json = await JsonSerializerWrapperForString.SerializeWrapper(testObj, options);
-            testObj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+            testObj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(json, options);
             testObj.Verify();
 
             json = "{}";
-            await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+            await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(json, options);
         }
 
         // Use a common options instance to encourage additional metadata collisions across types. Also since
@@ -132,9 +126,6 @@ namespace System.Text.Json.Serialization.Tests
 
         [Fact]
         [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", RuntimeConfiguration.Checked)]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task MultipleTypes()
         {
             async Task Serialize<T>(object[] args)
index 055ce4a..679ddca 100644 (file)
@@ -114,14 +114,11 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task ExtensionDataProperty_CannotBindTo_CtorParam()
         {
             InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(() => JsonSerializerWrapperForString.DeserializeWrapper<Class_ExtData_CtorParam>("{}"));
-            string exStr = ex.ToString(); // System.InvalidOperationException: 'The extension data property 'System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement] ExtensionData' on type 'System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam' cannot bind with a parameter in constructor 'Void .ctor(System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement])'.'
-            Assert.Contains("System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement] ExtensionData", exStr);
+            string exStr = ex.ToString(); // System.InvalidOperationException: 'The extension data property 'ExtensionData' on type 'System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam' cannot bind with a parameter in the deserialization constructor.'
+            Assert.Contains("ExtensionData", exStr);
             Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam", exStr);
         }
 
@@ -274,21 +271,21 @@ namespace System.Text.Json.Serialization.Tests
 
             // Baseline (no exception)
             {
-                var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(@"{""mydecimal"":1}", options);
+                var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(@"{""mydecimal"":1}", options);
                 Assert.Equal(1, obj.MyDecimal);
             }
 
             {
-                var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(@"{""MYDECIMAL"":1}", options);
+                var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(@"{""MYDECIMAL"":1}", options);
                 Assert.Equal(1, obj.MyDecimal);
             }
 
             JsonException e;
 
-            e = await Assert.ThrowsAsync<JsonException>(() => JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(@"{""mydecimal"":bad}", options));
+            e = await Assert.ThrowsAsync<JsonException>(() => JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(@"{""mydecimal"":bad}", options));
             Assert.Equal("$.mydecimal", e.Path);
 
-            e = await Assert.ThrowsAsync<JsonException>(() => JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(@"{""MYDECIMAL"":bad}", options));
+            e = await Assert.ThrowsAsync<JsonException>(() => JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(@"{""MYDECIMAL"":bad}", options));
             Assert.Equal("$.MYDECIMAL", e.Path);
         }
 
index 5f2da8c..c04815d 100644 (file)
@@ -291,9 +291,6 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task OtherPropertiesAreSet()
         {
             var personClass = await JsonSerializerWrapperForString.DeserializeWrapper<Person_Class>(Person_Class.s_json);
@@ -312,9 +309,6 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task ExtraProperties_GoInExtensionData_IfPresent()
         {
             Point_2D_With_ExtData point = await JsonSerializerWrapperForString.DeserializeWrapper<Point_2D_With_ExtData>(@"{""X"":1,""y"":2,""b"":3}");
@@ -368,7 +362,7 @@ namespace System.Text.Json.Serialization.Tests
         [Fact]
         public async Task NumerousSimpleAndComplexParameters()
         {
-            var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_json);
+            var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(ObjWCtorMixedParams.s_json);
             obj.Verify();
         }
 
@@ -565,7 +559,7 @@ namespace System.Text.Json.Serialization.Tests
 #endif
         public async Task TupleDeserializationWorks_ClassWithParameterizedCtor()
         {
-            string classJson = ClassWithConstructor_SimpleAndComplexParameters.s_json;
+            string classJson = ObjWCtorMixedParams.s_json;
 
             StringBuilder sb = new StringBuilder();
             sb.Append("{");
@@ -579,13 +573,13 @@ namespace System.Text.Json.Serialization.Tests
             string complexTupleJson = sb.ToString();
 
             var complexTuple = await JsonSerializerWrapperForString.DeserializeWrapper<Tuple<
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters>>(complexTupleJson);
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams>>(complexTupleJson);
 
             complexTuple.Item1.Verify();
             complexTuple.Item2.Verify();
@@ -793,9 +787,6 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task LastParameterWins_DoesNotGoToExtensionData()
         {
             string json = @"{
@@ -823,9 +814,6 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task HonorExtensionDataGeneric()
         {
             var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper<SimpleClassWithParameterizedCtor_GenericDictionary_JsonElementExt>(@"{""key"": ""value""}");
index ac5cd60..92b3b48 100644 (file)
@@ -12,9 +12,6 @@ namespace System.Text.Json.Serialization.Tests
     {
         [Fact]
         [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", RuntimeConfiguration.Checked)]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task ReadSimpleObjectAsync()
         {
             async Task RunTestAsync<T>(byte[] testData)
@@ -37,7 +34,7 @@ namespace System.Text.Json.Serialization.Tests
             // Simple models can be deserialized.
             tasks[0] = Task.Run(async () => await RunTestAsync<Parameterized_IndexViewModel_Immutable>(Parameterized_IndexViewModel_Immutable.s_data));
             // Complex models can be deserialized.
-            tasks[1] = Task.Run(async () => await RunTestAsync<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_data));
+            tasks[1] = Task.Run(async () => await RunTestAsync<ObjWCtorMixedParams>(ObjWCtorMixedParams.s_data));
             tasks[2] = Task.Run(async () => await RunTestAsync<Parameterized_Class_With_ComplexTuple>(Parameterized_Class_With_ComplexTuple.s_data));
             // JSON that doesn't bind to ctor args are matched with properties or ignored (as appropriate).
             tasks[3] = Task.Run(async () => await RunTestAsync<Person_Class>(Person_Class.s_data));
@@ -62,9 +59,6 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task ReadSimpleObjectWithTrailingTriviaAsync()
         {
             async Task RunTestAsync<T>(string testData)
@@ -89,7 +83,7 @@ namespace System.Text.Json.Serialization.Tests
             // Simple models can be deserialized.
             tasks[0] = Task.Run(async () => await RunTestAsync<Parameterized_IndexViewModel_Immutable>(Parameterized_IndexViewModel_Immutable.s_json));
             // Complex models can be deserialized.
-            tasks[1] = Task.Run(async () => await RunTestAsync<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_json));
+            tasks[1] = Task.Run(async () => await RunTestAsync<ObjWCtorMixedParams>(ObjWCtorMixedParams.s_json));
             tasks[2] = Task.Run(async () => await RunTestAsync<Parameterized_Class_With_ComplexTuple>(Parameterized_Class_With_ComplexTuple.s_json));
             // JSON that doesn't bind to ctor args are matched with properties or ignored (as appropriate).
             tasks[3] = Task.Run(async () => await RunTestAsync<Person_Class>(Person_Class.s_json));
@@ -157,9 +151,6 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
-        [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
         public async Task ExerciseStreamCodePaths()
         {
             static string GetPropertyName(int index) =>
diff --git a/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs b/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs
new file mode 100644 (file)
index 0000000..efa6bfb
--- /dev/null
@@ -0,0 +1,1466 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Reflection;
+using System.Text.Encodings.Web;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public abstract class ExtensionDataTests : SerializerTests
+    {
+        public ExtensionDataTests(JsonSerializerWrapperForString serializerWrapper) : base(serializerWrapper, null) { }
+
+        [Fact]
+        public async Task EmptyPropertyName_WinsOver_ExtensionDataEmptyPropertyName()
+        {
+            string json = @"{"""":1}";
+
+            ClassWithEmptyPropertyNameAndExtensionProperty obj;
+
+            // Create a new options instances to re-set any caches.
+            JsonSerializerOptions options = new JsonSerializerOptions();
+
+            // Verify the real property wins over the extension data property.
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+            Assert.Equal(1, obj.MyInt1);
+            Assert.Null(obj.MyOverflow);
+        }
+
+        [Fact]
+        public async Task EmptyPropertyNameInExtensionData()
+        {
+            {
+                string json = @"{"""":42}";
+                EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<EmptyClassWithExtensionProperty>(json);
+                Assert.Equal(1, obj.MyOverflow.Count);
+                Assert.Equal(42, obj.MyOverflow[""].GetInt32());
+            }
+
+            {
+                // Verify that last-in wins.
+                string json = @"{"""":42, """":43}";
+                EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<EmptyClassWithExtensionProperty>(json);
+                Assert.Equal(1, obj.MyOverflow.Count);
+                Assert.Equal(43, obj.MyOverflow[""].GetInt32());
+            }
+        }
+
+        [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+        [ActiveIssue("Needs SimpleTestClass support.")]
+#endif
+        public async Task ExtensionPropertyNotUsed()
+        {
+            string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
+            ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+            Assert.Null(obj.MyOverflow);
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyRoundTrip()
+        {
+            ClassWithExtensionProperty obj;
+
+            {
+                string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
+                obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+                Verify();
+            }
+
+            // Round-trip the json.
+            {
+                string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+                obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+                Verify();
+
+                // The json should not contain the dictionary name.
+                Assert.DoesNotContain(nameof(ClassWithExtensionProperty.MyOverflow), json);
+            }
+
+            void Verify()
+            {
+                Assert.NotNull(obj.MyOverflow);
+                Assert.Equal(1, obj.MyInt);
+                Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+
+                JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
+
+                // Verify a couple properties
+                Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
+                Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
+            }
+        }
+
+        [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+        [ActiveIssue("Needs SimpleTestClass support.")]
+#endif
+        public async Task ExtensionFieldNotUsed()
+        {
+            string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
+            ClassWithExtensionField obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionField>(json);
+            Assert.Null(obj.MyOverflow);
+        }
+
+        [Fact]
+        public async Task ExtensionFieldRoundTrip()
+        {
+            ClassWithExtensionField obj;
+
+            {
+                string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
+                obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionField>(json);
+                Verify();
+            }
+
+            // Round-trip the json.
+            {
+                string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+                obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionField>(json);
+                Verify();
+
+                // The json should not contain the dictionary name.
+                Assert.DoesNotContain(nameof(ClassWithExtensionField.MyOverflow), json);
+            }
+
+            void Verify()
+            {
+                Assert.NotNull(obj.MyOverflow);
+                Assert.Equal(1, obj.MyInt);
+                Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+
+                JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
+
+                // Verify a couple properties
+                Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
+                Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
+            }
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyIgnoredWhenWritingDefault()
+        {
+            string expected = @"{}";
+            string actual = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithExtensionPropertyAsObject());
+            Assert.Equal(expected, actual);
+        }
+
+        [Fact]
+        public async Task MultipleExtensionPropertyIgnoredWhenWritingDefault()
+        {
+            var obj = new ClassWithMultipleDictionaries();
+            string actual = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal("{\"ActualDictionary\":null}", actual);
+
+            obj = new ClassWithMultipleDictionaries
+            {
+                ActualDictionary = new Dictionary<string, object>()
+            };
+            actual = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal("{\"ActualDictionary\":{}}", actual);
+
+            obj = new ClassWithMultipleDictionaries
+            {
+                MyOverflow = new Dictionary<string, object>
+                {
+                    { "test", "value" }
+                }
+            };
+            actual = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal("{\"ActualDictionary\":null,\"test\":\"value\"}", actual);
+
+            obj = new ClassWithMultipleDictionaries
+            {
+                ActualDictionary = new Dictionary<string, object>(),
+                MyOverflow = new Dictionary<string, object>
+                {
+                    { "test", "value" }
+                }
+            };
+            actual = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal("{\"ActualDictionary\":{},\"test\":\"value\"}", actual);
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyInvalidJsonFail()
+        {
+            const string BadJson = @"{""Good"":""OK"",""Bad"":!}";
+
+            JsonException jsonException = await Assert.ThrowsAsync<JsonException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(BadJson));
+            Assert.Contains("Path: $.Bad | LineNumber: 0 | BytePositionInLine: 19.", jsonException.ToString());
+            Assert.NotNull(jsonException.InnerException);
+            Assert.IsAssignableFrom<JsonException>(jsonException.InnerException);
+            Assert.Contains("!", jsonException.InnerException.ToString());
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyAlreadyInstantiated()
+        {
+            Assert.NotNull(new ClassWithExtensionPropertyAlreadyInstantiated().MyOverflow);
+
+            string json = @"{""MyIntMissing"":2}";
+
+            ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+            Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyAsObject()
+        {
+            string json = @"{""MyIntMissing"":2}";
+
+            ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+            Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
+            Assert.Equal(2, ((JsonElement)obj.MyOverflow["MyIntMissing"]).GetInt32());
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyCamelCasing()
+        {
+            // Currently we apply no naming policy. If we do (such as a ExtensionPropertyNamingPolicy), we'd also have to add functionality to the JsonDocument.
+
+            ClassWithExtensionProperty obj;
+            const string jsonWithProperty = @"{""MyIntMissing"":1}";
+            const string jsonWithPropertyCamelCased = @"{""myIntMissing"":1}";
+
+            {
+                // Baseline Pascal-cased json + no casing option.
+                obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonWithProperty);
+                Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
+                string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+                Assert.Contains(@"""MyIntMissing"":1", json);
+            }
+
+            {
+                // Pascal-cased json + camel casing option.
+                JsonSerializerOptions options = new JsonSerializerOptions();
+                options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
+                options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+
+                obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonWithProperty, options);
+                Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
+                string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+                Assert.Contains(@"""MyIntMissing"":1", json);
+            }
+
+            {
+                // Baseline camel-cased json + no casing option.
+                obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonWithPropertyCamelCased);
+                Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
+                string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+                Assert.Contains(@"""myIntMissing"":1", json);
+            }
+
+            {
+                // Baseline camel-cased json + camel casing option.
+                JsonSerializerOptions options = new JsonSerializerOptions();
+                options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
+                options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+
+                obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonWithPropertyCamelCased, options);
+                Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
+                string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+                Assert.Contains(@"""myIntMissing"":1", json);
+            }
+        }
+
+        [Fact]
+        public async Task NullValuesIgnored()
+        {
+            const string json = @"{""MyNestedClass"":null}";
+            const string jsonMissing = @"{""MyNestedClassMissing"":null}";
+
+            {
+                // Baseline with no missing.
+                ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+                Assert.Null(obj.MyOverflow);
+
+                string outJson = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+                Assert.Contains(@"""MyNestedClass"":null", outJson);
+            }
+
+            {
+                // Baseline with missing.
+                ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonMissing);
+                Assert.Equal(1, obj.MyOverflow.Count);
+                Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyNestedClassMissing"].ValueKind);
+            }
+
+            {
+                JsonSerializerOptions options = new JsonSerializerOptions();
+                options.IgnoreNullValues = true;
+
+                ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonMissing, options);
+
+                // Currently we do not ignore nulls in the extension data. The JsonDocument would also need to support this mode
+                // for any lower-level nulls.
+                Assert.Equal(1, obj.MyOverflow.Count);
+                Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyNestedClassMissing"].ValueKind);
+            }
+        }
+
+        public class ClassWithInvalidExtensionProperty
+        {
+            [JsonExtensionData]
+            public Dictionary<string, int> MyOverflow { get; set; }
+        }
+
+        public class ClassWithTwoExtensionProperties
+        {
+            [JsonExtensionData]
+            public Dictionary<string, object> MyOverflow1 { get; set; }
+
+            [JsonExtensionData]
+            public Dictionary<string, object> MyOverflow2 { get; set; }
+        }
+
+        [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
+#endif
+        public async Task InvalidExtensionPropertyFail()
+        {
+            // Baseline
+            await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(@"{}");
+            await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(@"{}");
+
+            await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithInvalidExtensionProperty>(@"{}"));
+            await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithTwoExtensionProperties>(@"{}"));
+        }
+
+        public class ClassWithIgnoredData
+        {
+            [JsonExtensionData]
+            public Dictionary<string, object> MyOverflow { get; set; }
+
+            [JsonIgnore]
+            public int MyInt { get; set; }
+        }
+
+        [Fact]
+        public async Task IgnoredDataShouldNotBeExtensionData()
+        {
+            ClassWithIgnoredData obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithIgnoredData>(@"{""MyInt"":1}");
+
+            Assert.Equal(0, obj.MyInt);
+            Assert.Null(obj.MyOverflow);
+        }
+
+        public class ClassWithExtensionData<T>
+        {
+            [JsonExtensionData]
+            public T Overflow { get; set; }
+        }
+
+        public class CustomOverflowDictionary<T> : Dictionary<string, T>
+        {
+        }
+
+        public class DictionaryOverflowConverter : JsonConverter<Dictionary<string, object>>
+        {
+            public override Dictionary<string, object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override void Write(Utf8JsonWriter writer, Dictionary<string, object> value, JsonSerializerOptions options)
+            {
+                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+            }
+        }
+
+        public class JsonElementOverflowConverter : JsonConverter<Dictionary<string, JsonElement>>
+        {
+            public override Dictionary<string, JsonElement> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override void Write(Utf8JsonWriter writer, Dictionary<string, JsonElement> value, JsonSerializerOptions options)
+            {
+                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+            }
+        }
+
+        public class JsonObjectOverflowConverter : JsonConverter<JsonObject>
+        {
+            public override JsonObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override void Write(Utf8JsonWriter writer, JsonObject value, JsonSerializerOptions options)
+            {
+                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+            }
+        }
+
+        public class CustomObjectDictionaryOverflowConverter : JsonConverter<CustomOverflowDictionary<object>>
+        {
+            public override CustomOverflowDictionary<object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary<object> value, JsonSerializerOptions options)
+            {
+                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+            }
+        }
+
+        public class CustomJsonElementDictionaryOverflowConverter : JsonConverter<CustomOverflowDictionary<JsonElement>>
+        {
+            public override CustomOverflowDictionary<JsonElement> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary<JsonElement> value, JsonSerializerOptions options)
+            {
+                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+            }
+        }
+
+        [Theory]
+        [InlineData(typeof(Dictionary<string, object>), typeof(DictionaryOverflowConverter))]
+        [InlineData(typeof(Dictionary<string, JsonElement>), typeof(JsonElementOverflowConverter))]
+        [InlineData(typeof(CustomOverflowDictionary<object>), typeof(CustomObjectDictionaryOverflowConverter))]
+        [InlineData(typeof(CustomOverflowDictionary<JsonElement>), typeof(CustomJsonElementDictionaryOverflowConverter))]
+        public void ExtensionProperty_SupportsWritingToCustomSerializerWithOptions(Type overflowType, Type converterType)
+        {
+            typeof(ExtensionDataTests)
+                .GetMethod(nameof(ExtensionProperty_SupportsWritingToCustomSerializerWithOptionsInternal), BindingFlags.Static | BindingFlags.NonPublic)
+                .MakeGenericMethod(overflowType, converterType)
+                .Invoke(null, null);
+        }
+
+        private static void ExtensionProperty_SupportsWritingToCustomSerializerWithOptionsInternal<TDictionary, TConverter>()
+            where TDictionary : new()
+            where TConverter : JsonConverter, new()
+        {
+            var root = new ClassWithExtensionData<TDictionary>()
+            {
+                Overflow = new TDictionary()
+            };
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new TConverter());
+
+            string json = JsonSerializer.Serialize(root, options);
+            Assert.Equal(@"{""MyCustomOverflowWrite"":""OverflowValueWrite""}", json);
+        }
+
+        private interface IClassWithOverflow<T>
+        {
+            public T Overflow { get; set; }
+        }
+
+        public class ClassWithExtensionDataWithAttributedConverter : IClassWithOverflow<Dictionary<string, object>>
+        {
+            [JsonExtensionData]
+            [JsonConverter(typeof(DictionaryOverflowConverter))]
+            public Dictionary<string, object> Overflow { get; set; }
+        }
+
+        public class ClassWithJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow<Dictionary<string, JsonElement>>
+        {
+            [JsonExtensionData]
+            [JsonConverter(typeof(JsonElementOverflowConverter))]
+            public Dictionary<string, JsonElement> Overflow { get; set; }
+        }
+
+        public class ClassWithCustomElementExtensionDataWithAttributedConverter : IClassWithOverflow<CustomOverflowDictionary<object>>
+        {
+            [JsonExtensionData]
+            [JsonConverter(typeof(CustomObjectDictionaryOverflowConverter))]
+            public CustomOverflowDictionary<object> Overflow { get; set; }
+        }
+
+        public class ClassWithCustomJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow<CustomOverflowDictionary<JsonElement>>
+        {
+            [JsonExtensionData]
+            [JsonConverter(typeof(CustomJsonElementDictionaryOverflowConverter))]
+            public CustomOverflowDictionary<JsonElement> Overflow { get; set; }
+        }
+
+        [Theory]
+        [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary<string, object>))]
+        [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary<string, JsonElement>))]
+        [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<object>))]
+        [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<JsonElement>))]
+        public void ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverter(Type attributedType, Type dictionaryType)
+        {
+            typeof(ExtensionDataTests)
+                .GetMethod(nameof(ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverterInternal), BindingFlags.Static | BindingFlags.NonPublic)
+                .MakeGenericMethod(attributedType, dictionaryType)
+                .Invoke(null, null);
+        }
+
+        private static void ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverterInternal<TRoot, TDictionary>()
+            where TRoot : IClassWithOverflow<TDictionary>, new()
+            where TDictionary : new()
+        {
+            var root = new TRoot()
+            {
+                Overflow = new TDictionary()
+            };
+
+            string json = JsonSerializer.Serialize(root);
+            Assert.Equal(@"{""MyCustomOverflowWrite"":""OverflowValueWrite""}", json);
+        }
+
+        [Theory]
+        [InlineData(typeof(Dictionary<string, object>), typeof(DictionaryOverflowConverter), typeof(object))]
+        [InlineData(typeof(Dictionary<string, JsonElement>), typeof(JsonElementOverflowConverter), typeof(JsonElement))]
+        [InlineData(typeof(CustomOverflowDictionary<object>), typeof(CustomObjectDictionaryOverflowConverter), typeof(object))]
+        [InlineData(typeof(CustomOverflowDictionary<JsonElement>), typeof(CustomJsonElementDictionaryOverflowConverter), typeof(JsonElement))]
+        public void ExtensionProperty_IgnoresCustomSerializerWithOptions(Type overflowType, Type converterType, Type elementType)
+        {
+            typeof(ExtensionDataTests)
+                .GetMethod(nameof(ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal), BindingFlags.Static | BindingFlags.NonPublic)
+                .MakeGenericMethod(overflowType, elementType, converterType)
+                .Invoke(null, null);
+        }
+
+        [Fact]
+        public async Task ExtensionProperty_IgnoresCustomSerializerWithOptions_JsonObject()
+        {
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new JsonObjectOverflowConverter());
+
+            // A custom converter for JsonObject is not allowed on an extension property.
+            InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+                await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionData<JsonObject>>(@"{""TestKey"":""TestValue""}", options));
+
+            Assert.Contains("JsonObject", ex.ToString());
+        }
+
+        private static void ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal<TDictionary, TOverflowItem, TConverter>()
+            where TConverter : JsonConverter, new()
+            where TDictionary : IDictionary<string, TOverflowItem>
+        {
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new TConverter());
+
+            ClassWithExtensionData<TDictionary> obj
+                = JsonSerializer.Deserialize<ClassWithExtensionData<TDictionary>>(@"{""TestKey"":""TestValue""}", options);
+
+            Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString());
+        }
+
+        [Theory]
+        [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary<string, object>), typeof(object))]
+        [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary<string, JsonElement>), typeof(JsonElement))]
+        [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<object>), typeof(object))]
+        [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<JsonElement>), typeof(JsonElement))]
+        public void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverter(Type attributedType, Type dictionaryType, Type elementType)
+        {
+            typeof(ExtensionDataTests)
+                .GetMethod(nameof(ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal), BindingFlags.Static | BindingFlags.NonPublic)
+                .MakeGenericMethod(attributedType, dictionaryType, elementType)
+                .Invoke(null, null);
+        }
+
+        [Fact]
+        public async Task ExtensionProperty_IgnoresCustomSerializerWithExplicitConverter_JsonObject()
+        {
+            ClassWithExtensionData<JsonObject> obj
+                = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionData<JsonObject>>(@"{""TestKey"":""TestValue""}");
+
+            Assert.Equal("TestValue", obj.Overflow["TestKey"].GetValue<string>());
+        }
+
+        private static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal<TRoot, TDictionary, TOverflowItem>()
+            where TRoot : IClassWithOverflow<TDictionary>, new()
+            where TDictionary : IDictionary<string, TOverflowItem>
+        {
+            ClassWithExtensionData<TDictionary> obj
+                = JsonSerializer.Deserialize<ClassWithExtensionData<TDictionary>>(@"{""TestKey"":""TestValue""}");
+
+            Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString());
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyObjectValue_Empty()
+        {
+            ClassWithExtensionPropertyAlreadyInstantiated obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
+            Assert.Equal(@"{}", await JsonSerializerWrapperForString.SerializeWrapper(obj));
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyObjectValue_SameAsExtensionPropertyName()
+        {
+            const string json = @"{""MyOverflow"":{""Key1"":""V""}}";
+
+            // Deserializing directly into the overflow is not supported by design.
+            ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+
+            // The JSON is treated as normal overflow.
+            Assert.NotNull(obj.MyOverflow["MyOverflow"]);
+            Assert.Equal(json, await JsonSerializerWrapperForString.SerializeWrapper(obj));
+        }
+
+        public class ClassWithExtensionPropertyAsObjectAndNameProperty
+        {
+            public string Name { get; set; }
+
+            [JsonExtensionData]
+            public Dictionary<string, object> MyOverflow { get; set; }
+        }
+
+        public static IEnumerable<object[]> JsonSerializerOptions()
+        {
+            yield return new object[] { null };
+            yield return new object[] { new JsonSerializerOptions() };
+            yield return new object[] { new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement } };
+            yield return new object[] { new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode } };
+        }
+
+        [Theory]
+        [MemberData(nameof(JsonSerializerOptions))]
+        public async Task ExtensionPropertyDuplicateNames(JsonSerializerOptions options)
+        {
+            var obj = new ClassWithExtensionPropertyAsObjectAndNameProperty();
+            obj.Name = "Name1";
+
+            obj.MyOverflow = new Dictionary<string, object>();
+            obj.MyOverflow["Name"] = "Name2";
+
+            string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+            Assert.Equal(@"{""Name"":""Name1"",""Name"":""Name2""}", json);
+
+            // The overflow value comes last in the JSON so it overwrites the original value.
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObjectAndNameProperty>(json, options);
+            Assert.Equal("Name2", obj.Name);
+
+            // Since there was no overflow, this should be null.
+            Assert.Null(obj.MyOverflow);
+        }
+
+        [Theory]
+        [MemberData(nameof(JsonSerializerOptions))]
+        public async Task Null_SystemObject(JsonSerializerOptions options)
+        {
+            const string json = @"{""MissingProperty"":null}";
+
+            {
+                ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json, options);
+
+                // A null value maps to <object>, so the value is null.
+                object elem = obj.MyOverflow["MissingProperty"];
+                Assert.Null(elem);
+            }
+
+            {
+                ClassWithExtensionPropertyAsJsonObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonObject>(json, options);
+
+                JsonObject jObject = obj.MyOverflow;
+                JsonNode jNode = jObject["MissingProperty"];
+                // Since JsonNode is a reference type the value is null.
+                Assert.Null(jNode);
+            }
+        }
+
+        [Fact]
+        public async Task Null_JsonElement()
+        {
+            const string json = @"{""MissingProperty"":null}";
+
+            ClassWithExtensionPropertyAsJsonElement obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+            object elem = obj.MyOverflow["MissingProperty"];
+            // Since JsonElement is a struct, it treats null as JsonValueKind.Null.
+            Assert.IsType<JsonElement>(elem);
+            Assert.Equal(JsonValueKind.Null, ((JsonElement)elem).ValueKind);
+        }
+
+        [Fact]
+        public async Task Null_JsonObject()
+        {
+            const string json = @"{""MissingProperty"":null}";
+
+            ClassWithExtensionPropertyAsJsonObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonObject>(json);
+            object elem = obj.MyOverflow["MissingProperty"];
+            // Since JsonNode is a reference type the value is null.
+            Assert.Null(elem);
+        }
+
+        [Fact]
+        public async Task ExtensionPropertyObjectValue()
+        {
+            // Baseline
+            ClassWithExtensionPropertyAlreadyInstantiated obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
+            obj.MyOverflow.Add("test", new object());
+            obj.MyOverflow.Add("test1", 1);
+
+            Assert.Equal(@"{""test"":{},""test1"":1}", await JsonSerializerWrapperForString.SerializeWrapper(obj));
+        }
+
+        public class DummyObj
+        {
+            public string Prop { get; set; }
+        }
+
+        public struct DummyStruct
+        {
+            public string Prop { get; set; }
+        }
+
+        [Theory]
+        [MemberData(nameof(JsonSerializerOptions))]
+        public async Task ExtensionPropertyObjectValue_RoundTrip(JsonSerializerOptions options)
+        {
+            // Baseline
+            ClassWithExtensionPropertyAlreadyInstantiated obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}", options);
+            obj.MyOverflow.Add("test", new object());
+            obj.MyOverflow.Add("test1", 1);
+            obj.MyOverflow.Add("test2", "text");
+            obj.MyOverflow.Add("test3", new DummyObj() { Prop = "ObjectProp" });
+            obj.MyOverflow.Add("test4", new DummyStruct() { Prop = "StructProp" });
+            obj.MyOverflow.Add("test5", new Dictionary<string, object>() { { "Key", "Value" }, { "Key1", "Value1" }, });
+
+            string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            ClassWithExtensionPropertyAlreadyInstantiated roundTripObj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAlreadyInstantiated>(json, options);
+
+            Assert.Equal(6, roundTripObj.MyOverflow.Count);
+
+            if (options?.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode)
+            {
+                Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test"]);
+                Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test1"]);
+                Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test2"]);
+                Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test3"]);
+
+                Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test"]);
+
+                Assert.IsAssignableFrom<JsonValue>(roundTripObj.MyOverflow["test1"]);
+                Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue<int>());
+                Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue<long>());
+
+                Assert.IsAssignableFrom<JsonValue>(roundTripObj.MyOverflow["test2"]);
+                Assert.Equal("text", ((JsonValue)roundTripObj.MyOverflow["test2"]).GetValue<string>());
+
+                Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test3"]);
+                Assert.Equal("ObjectProp", ((JsonObject)roundTripObj.MyOverflow["test3"])["Prop"].GetValue<string>());
+
+                Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test4"]);
+                Assert.Equal("StructProp", ((JsonObject)roundTripObj.MyOverflow["test4"])["Prop"].GetValue<string>());
+
+                Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test5"]);
+                Assert.Equal("Value", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key"].GetValue<string>());
+                Assert.Equal("Value1", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key1"].GetValue<string>());
+            }
+            else
+            {
+                Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test"]);
+                Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test1"]);
+                Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test2"]);
+                Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test3"]);
+
+                Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test"]).ValueKind);
+
+                Assert.Equal(JsonValueKind.Number, ((JsonElement)roundTripObj.MyOverflow["test1"]).ValueKind);
+                Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt32());
+                Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt64());
+
+                Assert.Equal(JsonValueKind.String, ((JsonElement)roundTripObj.MyOverflow["test2"]).ValueKind);
+                Assert.Equal("text", ((JsonElement)roundTripObj.MyOverflow["test2"]).GetString());
+
+                Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test3"]).ValueKind);
+                Assert.Equal("ObjectProp", ((JsonElement)roundTripObj.MyOverflow["test3"]).GetProperty("Prop").GetString());
+
+                Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test4"]).ValueKind);
+                Assert.Equal("StructProp", ((JsonElement)roundTripObj.MyOverflow["test4"]).GetProperty("Prop").GetString());
+
+                Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test5"]).ValueKind);
+                Assert.Equal("Value", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key").GetString());
+                Assert.Equal("Value1", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key1").GetString());
+            }
+        }
+
+        [Fact]
+        public async Task DeserializeIntoJsonObjectProperty()
+        {
+            string json = @"{""MyDict"":{""Property1"":1}}";
+            ClassWithExtensionPropertyAsJsonObject obj =
+                await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonObject>(json);
+
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(1, obj.MyOverflow["MyDict"]["Property1"].GetValue<int>());
+        }
+
+        [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
+#endif
+
+        public async Task DeserializeIntoSystemObjectProperty()
+        {
+            string json = @"{""MyDict"":{""Property1"":1}}";
+
+            await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+                await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsSystemObject>(json));
+
+            // Cannot deserialize into System.Object overflow even if UnknownTypeHandling is set to use JsonNode.
+            var options = new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode };
+            await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+                await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsSystemObject>(json));
+        }
+
+        public class ClassWithReference
+        {
+            [JsonExtensionData]
+            public Dictionary<string, JsonElement> MyOverflow { get; set; }
+
+            public ClassWithExtensionProperty MyReference { get; set; }
+        }
+
+        [Theory]
+        [InlineData(@"{""MyIntMissing"":2,""MyReference"":{""MyIntMissingChild"":3}}")]
+        [InlineData(@"{""MyReference"":{""MyIntMissingChild"":3},""MyIntMissing"":2}")]
+        [InlineData(@"{""MyReference"":{""MyNestedClass"":null,""MyInt"":0,""MyIntMissingChild"":3},""MyIntMissing"":2}")]
+        public async Task NestedClass(string json)
+        {
+            ClassWithReference obj;
+
+            void Verify()
+            {
+                Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
+                Assert.Equal(1, obj.MyOverflow.Count);
+                Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+
+                ClassWithExtensionProperty child = obj.MyReference;
+
+                Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
+                Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
+                Assert.Equal(1, child.MyOverflow.Count);
+                Assert.Equal(3, child.MyOverflow["MyIntMissingChild"].GetInt32());
+                Assert.Null(child.MyNestedClass);
+                Assert.Equal(0, child.MyInt);
+            }
+
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithReference>(json);
+            Verify();
+
+            // Round-trip the json and verify.
+            json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithReference>(json);
+            Verify();
+        }
+
+        public class ParentClassWithObject
+        {
+            public string Text { get; set; }
+            public ChildClassWithObject Child { get; set; }
+
+            [JsonExtensionData]
+            public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
+        }
+
+        public class ChildClassWithObject
+        {
+            public int Number { get; set; }
+
+            [JsonExtensionData]
+            public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
+        }
+
+        [Fact]
+        public async Task NestedClassWithObjectExtensionDataProperty()
+        {
+            var child = new ChildClassWithObject { Number = 2 };
+            child.ExtensionData.Add("SpecialInformation", "I am child class");
+
+            var parent = new ParentClassWithObject { Text = "Hello World" };
+            parent.ExtensionData.Add("SpecialInformation", "I am parent class");
+            parent.Child = child;
+
+            // The extension data is based on the raw strings added above and not JsonElement.
+            Assert.Equal("Hello World", parent.Text);
+            Assert.IsType<string>(parent.ExtensionData["SpecialInformation"]);
+            Assert.Equal("I am parent class", (string)parent.ExtensionData["SpecialInformation"]);
+            Assert.Equal(2, parent.Child.Number);
+            Assert.IsType<string>(parent.Child.ExtensionData["SpecialInformation"]);
+            Assert.Equal("I am child class", (string)parent.Child.ExtensionData["SpecialInformation"]);
+
+            // Round-trip and verify. Extension data is now based on JsonElement.
+            string json = await JsonSerializerWrapperForString.SerializeWrapper(parent);
+            parent = await JsonSerializerWrapperForString.DeserializeWrapper<ParentClassWithObject>(json);
+
+            Assert.Equal("Hello World", parent.Text);
+            Assert.IsType<JsonElement>(parent.ExtensionData["SpecialInformation"]);
+            Assert.Equal("I am parent class", ((JsonElement)parent.ExtensionData["SpecialInformation"]).GetString());
+            Assert.Equal(2, parent.Child.Number);
+            Assert.IsType<JsonElement>(parent.Child.ExtensionData["SpecialInformation"]);
+            Assert.Equal("I am child class", ((JsonElement)parent.Child.ExtensionData["SpecialInformation"]).GetString());
+        }
+
+        public class ParentClassWithJsonElement
+        {
+            public string Text { get; set; }
+
+            public List<ChildClassWithJsonElement> Children { get; set; } = new List<ChildClassWithJsonElement>();
+
+            [JsonExtensionData]
+            // Use SortedDictionary as verification of supporting derived dictionaries.
+            public SortedDictionary<string, JsonElement> ExtensionData { get; set; } = new SortedDictionary<string, JsonElement>();
+        }
+
+        public class ChildClassWithJsonElement
+        {
+            public int Number { get; set; }
+
+            [JsonExtensionData]
+            public Dictionary<string, JsonElement> ExtensionData { get; set; } = new Dictionary<string, JsonElement>();
+        }
+
+        [Fact]
+        public async Task NestedClassWithJsonElementExtensionDataProperty()
+        {
+            var child = new ChildClassWithJsonElement { Number = 4 };
+            child.ExtensionData.Add("SpecialInformation", JsonDocument.Parse(await JsonSerializerWrapperForString.SerializeWrapper("I am child class")).RootElement);
+
+            var parent = new ParentClassWithJsonElement { Text = "Hello World" };
+            parent.ExtensionData.Add("SpecialInformation", JsonDocument.Parse(await JsonSerializerWrapperForString.SerializeWrapper("I am parent class")).RootElement);
+            parent.Children.Add(child);
+
+            Verify();
+
+            // Round-trip and verify.
+            string json = await JsonSerializerWrapperForString.SerializeWrapper(parent);
+            parent = await JsonSerializerWrapperForString.DeserializeWrapper<ParentClassWithJsonElement>(json);
+            Verify();
+
+            void Verify()
+            {
+                Assert.Equal("Hello World", parent.Text);
+                Assert.Equal("I am parent class", parent.ExtensionData["SpecialInformation"].GetString());
+                Assert.Equal(1, parent.Children.Count);
+                Assert.Equal(4, parent.Children[0].Number);
+                Assert.Equal("I am child class", parent.Children[0].ExtensionData["SpecialInformation"].GetString());
+            }
+        }
+
+        [Fact]
+        public async Task DeserializeIntoObjectProperty()
+        {
+            ClassWithExtensionPropertyAsObject obj;
+            string json;
+
+            // Baseline dictionary.
+            json = @"{""MyDict"":{""Property1"":1}}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyDict"]).EnumerateObject().First().Value.GetInt32());
+
+            // Attempt to deserialize directly into the overflow property; this is just added as a normal missing property like MyDict above.
+            json = @"{""MyOverflow"":{""Property1"":1}}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyOverflow"]).EnumerateObject().First().Value.GetInt32());
+
+            // Attempt to deserialize null into the overflow property. This is also treated as a missing property.
+            json = @"{""MyOverflow"":null}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Null(obj.MyOverflow["MyOverflow"]);
+
+            // Attempt to deserialize object into the overflow property. This is also treated as a missing property.
+            json = @"{""MyOverflow"":{}}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(JsonValueKind.Object, ((JsonElement)obj.MyOverflow["MyOverflow"]).ValueKind);
+        }
+
+        [Fact]
+        public async Task DeserializeIntoMultipleDictionaries()
+        {
+            ClassWithMultipleDictionaries obj;
+            string json;
+
+            // Baseline dictionary.
+            json = @"{""ActualDictionary"":{""Key"": {""Property0"":-1}},""MyDict"":{""Property1"":1}}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithMultipleDictionaries>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyDict"]).EnumerateObject().First().Value.GetInt32());
+            Assert.Equal(1, obj.ActualDictionary.Count);
+            Assert.Equal(-1, ((JsonElement)obj.ActualDictionary["Key"]).EnumerateObject().First().Value.GetInt32());
+
+            // Attempt to deserialize null into the dictionary and overflow property. This is also treated as a missing property.
+            json = @"{""ActualDictionary"":null,""MyOverflow"":null}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithMultipleDictionaries>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Null(obj.MyOverflow["MyOverflow"]);
+            Assert.Null(obj.ActualDictionary);
+
+            // Attempt to deserialize object into the dictionary and overflow property. This is also treated as a missing property.
+            json = @"{""ActualDictionary"":{},""MyOverflow"":{}}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithMultipleDictionaries>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(JsonValueKind.Object, ((JsonElement)obj.MyOverflow["MyOverflow"]).ValueKind);
+            Assert.Equal(0, obj.ActualDictionary.Count);
+        }
+
+        [Fact]
+        public async Task DeserializeIntoJsonElementProperty()
+        {
+            ClassWithExtensionPropertyAsJsonElement obj;
+            string json;
+
+            // Baseline dictionary.
+            json = @"{""MyDict"":{""Property1"":1}}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(1, obj.MyOverflow["MyDict"].EnumerateObject().First().Value.GetInt32());
+
+            // Attempt to deserialize directly into the overflow property; this is just added as a normal missing property like MyDict above.
+            json = @"{""MyOverflow"":{""Property1"":1}}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(1, obj.MyOverflow["MyOverflow"].EnumerateObject().First().Value.GetInt32());
+
+            // Attempt to deserialize null into the overflow property. This is also treated as a missing property.
+            json = @"{""MyOverflow"":null}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyOverflow"].ValueKind);
+
+            // Attempt to deserialize object into the overflow property. This is also treated as a missing property.
+            json = @"{""MyOverflow"":{}}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(JsonValueKind.Object, obj.MyOverflow["MyOverflow"].ValueKind);
+        }
+
+        [Fact]
+        public async Task SerializerOutputRoundtripsWhenEscaping()
+        {
+            string jsonString = "{\"\u6C49\u5B57\":\"abc\",\"Class\":{\"\u6F22\u5B57\":\"xyz\"},\"\u62DC\u6258\":{\"\u62DC\u6258\u62DC\u6258\":1}}";
+
+            ClassWithEscapedProperty input = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEscapedProperty>(jsonString);
+
+            Assert.Equal("abc", input.\u6C49\u5B57);
+            Assert.Equal("xyz", input.Class.\u6F22\u5B57);
+
+            string normalizedString = await JsonSerializerWrapperForString.SerializeWrapper(input);
+
+            Assert.Equal(normalizedString, await JsonSerializerWrapperForString.SerializeWrapper(await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEscapedProperty>(normalizedString)));
+        }
+
+        public class ClassWithEscapedProperty
+        {
+            public string \u6C49\u5B57 { get; set; }
+            public NestedClassWithEscapedProperty Class { get; set; }
+
+            [JsonExtensionData]
+            public Dictionary<string, object> Overflow { get; set; }
+        }
+
+        public class NestedClassWithEscapedProperty
+        {
+            public string \u6F22\u5B57 { get; set; }
+        }
+
+        public class ClassWithInvalidExtensionPropertyStringString
+        {
+            [JsonExtensionData]
+            public Dictionary<string, string> MyOverflow { get; set; }
+        }
+
+        public class ClassWithInvalidExtensionPropertyObjectString
+        {
+            [JsonExtensionData]
+            public Dictionary<DummyObj, string> MyOverflow { get; set; }
+        }
+
+        public class ClassWithInvalidExtensionPropertyStringJsonNode
+        {
+            [JsonExtensionData]
+            public Dictionary<string, JsonNode> MyOverflow { get; set; }
+        }
+
+        [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
+#endif
+        public async Task ExtensionProperty_InvalidDictionary()
+        {
+            var obj1 = new ClassWithInvalidExtensionPropertyStringString();
+            await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj1));
+
+            var obj2 = new ClassWithInvalidExtensionPropertyObjectString();
+            await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj2));
+
+            var obj3 = new ClassWithInvalidExtensionPropertyStringJsonNode();
+            await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj3));
+        }
+
+        public class ClassWithExtensionPropertyAlreadyInstantiated
+        {
+            public ClassWithExtensionPropertyAlreadyInstantiated()
+            {
+                MyOverflow = new Dictionary<string, object>();
+            }
+
+            [JsonExtensionData]
+            public Dictionary<string, object> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyAsObject
+        {
+            [JsonExtensionData]
+            public Dictionary<string, object> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyAsJsonElement
+        {
+            [JsonExtensionData]
+            public Dictionary<string, JsonElement> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyAsJsonObject
+        {
+            [JsonExtensionData]
+            public JsonObject MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyAsSystemObject
+        {
+            [JsonExtensionData]
+            public object MyOverflow { get; set; }
+        }
+
+        public class ClassWithMultipleDictionaries
+        {
+            [JsonExtensionData]
+            public Dictionary<string, object> MyOverflow { get; set; }
+
+            public Dictionary<string, object> ActualDictionary { get; set; }
+        }
+
+        [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
+#endif
+        public async Task DeserializeIntoImmutableDictionaryProperty()
+        {
+            // baseline
+            await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsImmutable>(@"{}");
+            await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsImmutableJsonElement>(@"{}");
+            await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyPrivateConstructor>(@"{}");
+            await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyPrivateConstructorJsonElement>(@"{}");
+
+            await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsImmutable>("{\"hello\":\"world\"}"));
+            await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsImmutableJsonElement>("{\"hello\":\"world\"}"));
+            await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyPrivateConstructor>("{\"hello\":\"world\"}"));
+            await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyPrivateConstructorJsonElement>("{\"hello\":\"world\"}"));
+            await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyCustomIImmutable>("{\"hello\":\"world\"}"));
+            await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyCustomIImmutableJsonElement>("{\"hello\":\"world\"}"));
+        }
+
+        [Fact]
+        public async Task SerializeIntoImmutableDictionaryProperty()
+        {
+            // attempt to serialize a null immutable dictionary
+            string expectedJson = "{}";
+            var obj = new ClassWithExtensionPropertyAsImmutable();
+            var json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal(expectedJson, json);
+
+            // attempt to serialize an empty immutable dictionary
+            expectedJson = "{}";
+            obj = new ClassWithExtensionPropertyAsImmutable();
+            obj.MyOverflow = ImmutableDictionary<string, object>.Empty;
+            json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal(expectedJson, json);
+
+            // attempt to serialize a populated immutable dictionary
+            expectedJson = "{\"hello\":\"world\"}";
+            obj = new ClassWithExtensionPropertyAsImmutable();
+            var dictionaryStringObject = new Dictionary<string, object> { { "hello", "world" } };
+            obj.MyOverflow = ImmutableDictionary.CreateRange(dictionaryStringObject);
+            json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal(expectedJson, json);
+        }
+
+        public class ClassWithExtensionPropertyAsImmutable
+        {
+            [JsonExtensionData]
+            public ImmutableDictionary<string, object> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyAsImmutableJsonElement
+        {
+            [JsonExtensionData]
+            public ImmutableDictionary<string, JsonElement> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyPrivateConstructor
+        {
+            [JsonExtensionData]
+            public GenericIDictionaryWrapperPrivateConstructor<string, object> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyPrivateConstructorJsonElement
+        {
+            [JsonExtensionData]
+            public GenericIDictionaryWrapperPrivateConstructor<string, JsonElement> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyCustomIImmutable
+        {
+            [JsonExtensionData]
+            public GenericIImmutableDictionaryWrapper<string, object> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyCustomIImmutableJsonElement
+        {
+            [JsonExtensionData]
+            public GenericIImmutableDictionaryWrapper<string, JsonElement> MyOverflow { get; set; }
+        }
+
+        [Theory]
+        [InlineData(typeof(ClassWithExtensionPropertyNoGenericParameters))]
+        [InlineData(typeof(ClassWithExtensionPropertyOneGenericParameter))]
+        [InlineData(typeof(ClassWithExtensionPropertyThreeGenericParameters))]
+        public async Task DeserializeIntoGenericDictionaryParameterCount(Type type)
+        {
+            object obj = await JsonSerializerWrapperForString.DeserializeWrapper("{\"hello\":\"world\"}", type);
+
+            IDictionary<string, object> extData = (IDictionary<string, object>)type.GetProperty("MyOverflow").GetValue(obj)!;
+            Assert.Equal("world", ((JsonElement)extData["hello"]).GetString());
+        }
+
+        public class ClassWithExtensionPropertyNoGenericParameters
+        {
+            [JsonExtensionData]
+            public StringToObjectIDictionaryWrapper MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyOneGenericParameter
+        {
+            [JsonExtensionData]
+            public StringToGenericIDictionaryWrapper<object> MyOverflow { get; set; }
+        }
+
+        public class ClassWithExtensionPropertyThreeGenericParameters
+        {
+            [JsonExtensionData]
+            public GenericIDictonaryWrapperThreeGenericParameters<string, object, string> MyOverflow { get; set; }
+        }
+
+        [Fact]
+        public async Task CustomObjectConverterInExtensionProperty()
+        {
+            const string Json = "{\"hello\": \"world\"}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new ObjectConverter());
+
+            ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(Json, options);
+            object overflowProp = obj.MyOverflow["hello"];
+            Assert.IsType<string>(overflowProp);
+            Assert.Equal("world!!!", ((string)overflowProp));
+
+            string newJson = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+            Assert.Equal("{\"hello\":\"world!!!\"}", newJson);
+        }
+
+        public class ObjectConverter : JsonConverter<object>
+        {
+            public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                return reader.GetString() + "!!!";
+            }
+
+            public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
+            {
+                // Since we are in a user-provided (not internal to S.T.Json) object converter,
+                // this converter will be called, not the internal string converter.
+                writer.WriteStringValue((string)value);
+            }
+        }
+
+        [Fact]
+        public async Task CustomJsonElementConverterInExtensionProperty()
+        {
+            const string Json = "{\"hello\": \"world\"}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new JsonElementConverter());
+
+            ClassWithExtensionPropertyAsJsonElement obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(Json, options);
+            JsonElement overflowProp = obj.MyOverflow["hello"];
+            Assert.Equal(JsonValueKind.Undefined, overflowProp.ValueKind);
+
+            string newJson = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+            Assert.Equal("{\"hello\":{\"Hi\":\"There\"}}", newJson);
+        }
+
+        public class JsonElementConverter : JsonConverter<JsonElement>
+        {
+            public override JsonElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                // Just return an empty JsonElement.
+                reader.Skip();
+                return new JsonElement();
+            }
+
+            public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
+            {
+                // Write a string we can test against easily.
+                writer.WriteStartObject();
+                writer.WriteString("Hi", "There");
+                writer.WriteEndObject();
+            }
+        }
+
+        [Fact]
+        public async Task CustomJsonObjectConverterInExtensionProperty()
+        {
+            const string Json = "{\"hello\": \"world\"}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new JsonObjectConverter());
+
+            // A custom converter for JsonObject is not allowed on an extension property.
+            InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+                await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonObject>(Json, options));
+
+            Assert.Contains("JsonObject", ex.ToString());
+        }
+
+        public class JsonObjectConverter : JsonConverter<JsonObject>
+        {
+            public override JsonObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                // Just return an empty JsonElement.
+                reader.Skip();
+                return new JsonObject();
+            }
+
+            public override void Write(Utf8JsonWriter writer, JsonObject value, JsonSerializerOptions options)
+            {
+                // Write a string we can test against easily.
+                writer.WriteStartObject();
+                writer.WriteString("Hi", "There");
+                writer.WriteEndObject();
+            }
+        }
+
+        [Fact]
+        public async Task EmptyPropertyAndExtensionData_PropertyFirst()
+        {
+            // Verify any caching treats real property (with empty name) differently than a missing property.
+
+            ClassWithEmptyPropertyNameAndExtensionProperty obj;
+
+            // Create a new options instances to re-set any caches.
+            JsonSerializerOptions options = new JsonSerializerOptions();
+
+            // First use an empty property.
+            string json = @"{"""":43}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+            Assert.Equal(43, obj.MyInt1);
+            Assert.Null(obj.MyOverflow);
+
+            // Then populate cache with a missing property name.
+            json = @"{""DoesNotExist"":42}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+            Assert.Equal(0, obj.MyInt1);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(42, obj.MyOverflow["DoesNotExist"].GetInt32());
+        }
+
+        [Fact]
+        public async Task EmptyPropertyNameAndExtensionData_ExtDataFirst()
+        {
+            // Verify any caching treats real property (with empty name) differently than a missing property.
+
+            ClassWithEmptyPropertyNameAndExtensionProperty obj;
+
+            // Create a new options instances to re-set any caches.
+            JsonSerializerOptions options = new JsonSerializerOptions();
+
+            // First populate cache with a missing property name.
+            string json = @"{""DoesNotExist"":42}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+            Assert.Equal(0, obj.MyInt1);
+            Assert.Equal(1, obj.MyOverflow.Count);
+            Assert.Equal(42, obj.MyOverflow["DoesNotExist"].GetInt32());
+
+            // Then use an empty property.
+            json = @"{"""":43}";
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+            Assert.Equal(43, obj.MyInt1);
+            Assert.Null(obj.MyOverflow);
+        }
+
+        [Fact]
+        public async Task ExtensionDataDictionarySerialize_DoesNotHonor()
+        {
+            var options = new JsonSerializerOptions
+            {
+                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+            };
+
+            EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<EmptyClassWithExtensionProperty>(@"{""Key1"": 1}", options);
+
+            // Ignore naming policy for extension data properties by default.
+            Assert.False(obj.MyOverflow.ContainsKey("key1"));
+            Assert.Equal(1, obj.MyOverflow["Key1"].GetInt32());
+        }
+
+        [Theory]
+        [InlineData(0x1, 'v')]
+        [InlineData(0x1, '\u0467')]
+        [InlineData(0x10, 'v')]
+        [InlineData(0x10, '\u0467')]
+        [InlineData(0x100, 'v')]
+        [InlineData(0x100, '\u0467')]
+        [InlineData(0x1000, 'v')]
+        [InlineData(0x1000, '\u0467')]
+        [InlineData(0x10000, 'v')]
+        [InlineData(0x10000, '\u0467')]
+        public async Task LongPropertyNames(int propertyLength, char ch)
+        {
+            // Although the CLR may limit member length to 1023 bytes, the serializer doesn't have a hard limit.
+
+            string val = new string(ch, propertyLength);
+            string json = @"{""" + val + @""":1}";
+
+            EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<EmptyClassWithExtensionProperty>(json);
+
+            Assert.True(obj.MyOverflow.ContainsKey(val));
+
+            var options = new JsonSerializerOptions
+            {
+                // Avoid escaping '\u0467'.
+                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+            };
+
+            string jsonRoundTripped = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+            Assert.Equal(json, jsonRoundTripped);
+        }
+
+        public class EmptyClassWithExtensionProperty
+        {
+            [JsonExtensionData]
+            public IDictionary<string, JsonElement> MyOverflow { get; set; }
+        }
+
+        public class ClassWithEmptyPropertyNameAndExtensionProperty
+        {
+            [JsonPropertyName("")]
+            public int MyInt1 { get; set; }
+
+            [JsonExtensionData]
+            public IDictionary<string, JsonElement> MyOverflow { get; set; }
+        }
+    }
+}
index 0d2749c..890d61f 100644 (file)
@@ -923,7 +923,7 @@ namespace System.Text.Json.Serialization.Tests
         public int Int { get; set; }
     }
 
-    public class ClassWithConstructor_SimpleAndComplexParameters : ITestClassWithParameterizedCtor
+    public class ObjWCtorMixedParams : ITestClassWithParameterizedCtor
     {
         public byte MyByte { get; }
         public sbyte MySByte { get; set; }
@@ -961,7 +961,7 @@ namespace System.Text.Json.Serialization.Tests
         public ImmutableSortedSet<string> MyStringImmutableSortedSetT { get; }
         public List<string> MyListOfNullString { get; }
 
-        public ClassWithConstructor_SimpleAndComplexParameters(
+        public ObjWCtorMixedParams(
             byte myByte,
             char myChar,
             string myString,
@@ -1011,8 +1011,8 @@ namespace System.Text.Json.Serialization.Tests
             MyListOfNullString = myListOfNullString;
         }
 
-        public static ClassWithConstructor_SimpleAndComplexParameters GetInstance() =>
-            JsonSerializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(s_json);
+        public static ObjWCtorMixedParams GetInstance() =>
+            JsonSerializer.Deserialize<ObjWCtorMixedParams>(s_json);
 
         public static string s_json => $"{{{s_partialJson1},{s_partialJson2}}}";
 
@@ -1849,23 +1849,23 @@ namespace System.Text.Json.Serialization.Tests
     public class Parameterized_Class_With_ComplexTuple : ITestClassWithParameterizedCtor
     {
         public Tuple<
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters> MyTuple { get; }
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams> MyTuple { get; }
 
         public Parameterized_Class_With_ComplexTuple(
             Tuple<
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters,
-                ClassWithConstructor_SimpleAndComplexParameters> myTuple) => MyTuple = myTuple;
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams,
+                ObjWCtorMixedParams> myTuple) => MyTuple = myTuple;
 
         private const string s_inner_json = @"
             {
index 01a7e4d..f0d1cb8 100644 (file)
@@ -49,7 +49,7 @@ namespace System.Text.Json.SourceGeneration.Tests
         [JsonSerializable(typeof(Parameterized_WrapperForICollection))]
         [JsonSerializable(typeof(Point_2D_Struct))]
         [JsonSerializable(typeof(Point_2D_Struct_WithAttribute))]
-        [JsonSerializable(typeof(ClassWithConstructor_SimpleAndComplexParameters))]
+        [JsonSerializable(typeof(ObjWCtorMixedParams))]
         [JsonSerializable(typeof(Person_Class))]
         [JsonSerializable(typeof(Point_2D))]
         [JsonSerializable(typeof(Point_MultipleMembers_BindTo_OneConstructorParameter))]
@@ -123,6 +123,8 @@ namespace System.Text.Json.SourceGeneration.Tests
         [JsonSerializable(typeof(MyRecord))]
         [JsonSerializable(typeof(AgeRecord))]
         [JsonSerializable(typeof(JsonElement))]
+        [JsonSerializable(typeof(Parameterized_Class_With_ComplexTuple))]
+        [JsonSerializable(typeof(Parameterized_Person_Simple))]
         internal sealed partial class ConstructorTestsContext_Metadata : JsonSerializerContext
         {
         }
@@ -163,7 +165,7 @@ namespace System.Text.Json.SourceGeneration.Tests
         [JsonSerializable(typeof(Parameterized_WrapperForICollection))]
         [JsonSerializable(typeof(Point_2D_Struct))]
         [JsonSerializable(typeof(Point_2D_Struct_WithAttribute))]
-        [JsonSerializable(typeof(ClassWithConstructor_SimpleAndComplexParameters))]
+        [JsonSerializable(typeof(ObjWCtorMixedParams))]
         [JsonSerializable(typeof(Person_Class))]
         [JsonSerializable(typeof(Point_2D))]
         [JsonSerializable(typeof(Point_MultipleMembers_BindTo_OneConstructorParameter))]
@@ -237,6 +239,8 @@ namespace System.Text.Json.SourceGeneration.Tests
         [JsonSerializable(typeof(MyRecord))]
         [JsonSerializable(typeof(AgeRecord))]
         [JsonSerializable(typeof(JsonElement))]
+        [JsonSerializable(typeof(Parameterized_Class_With_ComplexTuple))]
+        [JsonSerializable(typeof(Parameterized_Person_Simple))]
         internal sealed partial class ConstructorTestsContext_Default : JsonSerializerContext
         {
         }
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ExtensionDataTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ExtensionDataTests.cs
new file mode 100644 (file)
index 0000000..22b67e7
--- /dev/null
@@ -0,0 +1,131 @@
+//Licensed to the .NET Foundation under one or more agreements.
+//The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Tests;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    public sealed partial class ExtensionDataTests_Metadata : ExtensionDataTests
+    {
+        public ExtensionDataTests_Metadata()
+            : base(new StringSerializerWrapper(ExtensionDataTestsContext_Metadata.Default, (options) => new ExtensionDataTestsContext_Metadata(options)))
+        {
+        }
+
+        [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
+        [JsonSerializable(typeof(ClassWithEmptyPropertyNameAndExtensionProperty))]
+        [JsonSerializable(typeof(EmptyClassWithExtensionProperty))]
+        [JsonSerializable(typeof(ClassWithExtensionProperty))]
+        [JsonSerializable(typeof(ClassWithExtensionField))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsObject))]
+        [JsonSerializable(typeof(ClassWithIgnoredData))]
+        [JsonSerializable(typeof(Dictionary<string, object>))]
+        [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
+        [JsonSerializable(typeof(CustomOverflowDictionary<object>))]
+        [JsonSerializable(typeof(CustomOverflowDictionary<JsonElement>))]
+        [JsonSerializable(typeof(ExtensionDataTests))]
+        [JsonSerializable(typeof(DictionaryOverflowConverter))]
+        [JsonSerializable(typeof(JsonElementOverflowConverter))]
+        [JsonSerializable(typeof(CustomObjectDictionaryOverflowConverter))]
+        [JsonSerializable(typeof(CustomJsonElementDictionaryOverflowConverter))]
+        [JsonSerializable(typeof(ClassWithExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(Dictionary<string, object>))]
+        [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
+        [JsonSerializable(typeof(CustomOverflowDictionary<object>))]
+        [JsonSerializable(typeof(CustomOverflowDictionary<JsonElement>))]
+        [JsonSerializable(typeof(ClassWithExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAlreadyInstantiated))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsObjectAndNameProperty))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsJsonObject))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsJsonElement))]
+        [JsonSerializable(typeof(ClassWithReference))]
+        [JsonSerializable(typeof(ParentClassWithObject))]
+        [JsonSerializable(typeof(ParentClassWithJsonElement))]
+        [JsonSerializable(typeof(ClassWithMultipleDictionaries))]
+        [JsonSerializable(typeof(ClassWithEscapedProperty))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsImmutable))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsImmutableJsonElement))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyPrivateConstructor))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyPrivateConstructorJsonElement))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyNoGenericParameters))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyOneGenericParameter))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyThreeGenericParameters))]
+        [JsonSerializable(typeof(JsonElement))]
+        [JsonSerializable(typeof(ClassWithExtensionData<JsonObject>))]
+        [JsonSerializable(typeof(int))]
+        [JsonSerializable(typeof(DummyObj))]
+        [JsonSerializable(typeof(DummyStruct))]
+        internal sealed partial class ExtensionDataTestsContext_Metadata : JsonSerializerContext
+        {
+        }
+    }
+
+    public sealed partial class ExtensionDataTests_Default : ExtensionDataTests
+    {
+        public ExtensionDataTests_Default()
+            : base(new StringSerializerWrapper(ExtensionDataTestsContext_Default.Default, (options) => new ExtensionDataTestsContext_Default(options)))
+        {
+        }
+
+        [JsonSerializable(typeof(ClassWithEmptyPropertyNameAndExtensionProperty))]
+        [JsonSerializable(typeof(EmptyClassWithExtensionProperty))]
+        [JsonSerializable(typeof(ClassWithExtensionProperty))]
+        [JsonSerializable(typeof(ClassWithExtensionField))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsObject))]
+        [JsonSerializable(typeof(ClassWithIgnoredData))]
+        [JsonSerializable(typeof(Dictionary<string, object>))]
+        [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
+        [JsonSerializable(typeof(CustomOverflowDictionary<object>))]
+        [JsonSerializable(typeof(CustomOverflowDictionary<JsonElement>))]
+        [JsonSerializable(typeof(ExtensionDataTests))]
+        [JsonSerializable(typeof(DictionaryOverflowConverter))]
+        [JsonSerializable(typeof(JsonElementOverflowConverter))]
+        [JsonSerializable(typeof(CustomObjectDictionaryOverflowConverter))]
+        [JsonSerializable(typeof(CustomJsonElementDictionaryOverflowConverter))]
+        [JsonSerializable(typeof(ClassWithExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(Dictionary<string, object>))]
+        [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
+        [JsonSerializable(typeof(CustomOverflowDictionary<object>))]
+        [JsonSerializable(typeof(CustomOverflowDictionary<JsonElement>))]
+        [JsonSerializable(typeof(ClassWithExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAlreadyInstantiated))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsObjectAndNameProperty))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsJsonObject))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsJsonElement))]
+        [JsonSerializable(typeof(ClassWithReference))]
+        [JsonSerializable(typeof(ParentClassWithObject))]
+        [JsonSerializable(typeof(ParentClassWithJsonElement))]
+        [JsonSerializable(typeof(ClassWithMultipleDictionaries))]
+        [JsonSerializable(typeof(ClassWithEscapedProperty))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsImmutable))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyAsImmutableJsonElement))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyPrivateConstructor))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyPrivateConstructorJsonElement))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyNoGenericParameters))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyOneGenericParameter))]
+        [JsonSerializable(typeof(ClassWithExtensionPropertyThreeGenericParameters))]
+        [JsonSerializable(typeof(JsonElement))]
+        [JsonSerializable(typeof(ClassWithExtensionData<JsonObject>))]
+        [JsonSerializable(typeof(int))]
+        [JsonSerializable(typeof(DummyObj))]
+        [JsonSerializable(typeof(DummyStruct))]
+        internal sealed partial class ExtensionDataTestsContext_Default : JsonSerializerContext
+        {
+        }
+    }
+}
index f0bdb59..01cb1a2 100644 (file)
@@ -38,6 +38,7 @@
     <Compile Include="..\Common\ConstructorTests\ConstructorTests.Exceptions.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.Exceptions.cs" />
     <Compile Include="..\Common\ConstructorTests\ConstructorTests.ParameterMatching.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.ParameterMatching.cs" />
     <Compile Include="..\Common\ConstructorTests\ConstructorTests.Stream.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.Stream.cs" />
+    <Compile Include="..\Common\ExtensionDataTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ExtensionDataTests.cs" />
     <Compile Include="..\Common\JsonSerializerWrapperForStream.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapperForStream.cs" />
     <Compile Include="..\Common\JsonSerializerWrapperForString.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapperForString.cs" />
     <Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />
@@ -67,6 +68,7 @@
     <Compile Include="JsonSerializerContextTests.cs" />
     <Compile Include="Serialization\CollectionTests.cs" />
     <Compile Include="Serialization\ConstructorTests.cs" />
+    <Compile Include="Serialization\ExtensionDataTests.cs" />
     <Compile Include="Serialization\JsonSerializerWrapper.cs" />
     <Compile Include="JsonTestHelper.cs" />
     <Compile Include="MetadataAndSerializationContextTests.cs" />
index 7a64f8e..00ca72a 100644 (file)
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Reflection;
-using System.Text.Encodings.Web;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using Xunit;
-
 namespace System.Text.Json.Serialization.Tests
 {
-    public static class ExtensionDataTests
+    public sealed partial class ExtensionDataTestsDynamic : ExtensionDataTests
     {
-        [Fact]
-        public static void EmptyPropertyName_WinsOver_ExtensionDataEmptyPropertyName()
-        {
-            string json = @"{"""":1}";
-
-            ClassWithEmptyPropertyNameAndExtensionProperty obj;
-
-            // Create a new options instances to re-set any caches.
-            JsonSerializerOptions options = new JsonSerializerOptions();
-
-            // Verify the real property wins over the extension data property.
-            obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
-            Assert.Equal(1, obj.MyInt1);
-            Assert.Null(obj.MyOverflow);
-        }
-
-        [Fact]
-        public static void EmptyPropertyNameInExtensionData()
-        {
-            {
-                string json = @"{"""":42}";
-                EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize<EmptyClassWithExtensionProperty>(json);
-                Assert.Equal(1, obj.MyOverflow.Count);
-                Assert.Equal(42, obj.MyOverflow[""].GetInt32());
-            }
-
-            {
-                // Verify that last-in wins.
-                string json = @"{"""":42, """":43}";
-                EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize<EmptyClassWithExtensionProperty>(json);
-                Assert.Equal(1, obj.MyOverflow.Count);
-                Assert.Equal(43, obj.MyOverflow[""].GetInt32());
-            }
-        }
-
-        [Fact]
-        public static void ExtensionPropertyNotUsed()
-        {
-            string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
-            ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
-            Assert.Null(obj.MyOverflow);
-        }
-
-        [Fact]
-        public static void ExtensionPropertyRoundTrip()
-        {
-            ClassWithExtensionProperty obj;
-
-            {
-                string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
-                obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
-                Verify();
-            }
-
-            // Round-trip the json.
-            {
-                string json = JsonSerializer.Serialize(obj);
-                obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
-                Verify();
-
-                // The json should not contain the dictionary name.
-                Assert.DoesNotContain(nameof(ClassWithExtensionProperty.MyOverflow), json);
-            }
-
-            void Verify()
-            {
-                Assert.NotNull(obj.MyOverflow);
-                Assert.Equal(1, obj.MyInt);
-                Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
-
-                JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
-
-                // Verify a couple properties
-                Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
-                Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
-            }
-        }
-
-        [Fact]
-        public static void ExtensionFieldNotUsed()
-        {
-            string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
-            ClassWithExtensionField obj = JsonSerializer.Deserialize<ClassWithExtensionField>(json);
-            Assert.Null(obj.MyOverflow);
-        }
-
-        [Fact]
-        public static void ExtensionFieldRoundTrip()
-        {
-            ClassWithExtensionField obj;
-
-            {
-                string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
-                obj = JsonSerializer.Deserialize<ClassWithExtensionField>(json);
-                Verify();
-            }
-
-            // Round-trip the json.
-            {
-                string json = JsonSerializer.Serialize(obj);
-                obj = JsonSerializer.Deserialize<ClassWithExtensionField>(json);
-                Verify();
-
-                // The json should not contain the dictionary name.
-                Assert.DoesNotContain(nameof(ClassWithExtensionField.MyOverflow), json);
-            }
-
-            void Verify()
-            {
-                Assert.NotNull(obj.MyOverflow);
-                Assert.Equal(1, obj.MyInt);
-                Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
-
-                JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
-
-                // Verify a couple properties
-                Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
-                Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
-            }
-        }
-
-        [Fact]
-        public static void ExtensionPropertyIgnoredWhenWritingDefault()
-        {
-            string expected = @"{}";
-            string actual = JsonSerializer.Serialize(new ClassWithExtensionPropertyAsObject());
-            Assert.Equal(expected, actual);
-        }
-
-        [Fact]
-        public static void MultipleExtensionPropertyIgnoredWhenWritingDefault()
-        {
-            var obj = new ClassWithMultipleDictionaries();
-            string actual = JsonSerializer.Serialize(obj);
-            Assert.Equal("{\"ActualDictionary\":null}", actual);
-
-            obj = new ClassWithMultipleDictionaries
-            {
-                ActualDictionary = new Dictionary<string, object>()
-            };
-            actual = JsonSerializer.Serialize(obj);
-            Assert.Equal("{\"ActualDictionary\":{}}", actual);
-
-            obj = new ClassWithMultipleDictionaries
-            {
-                MyOverflow = new Dictionary<string, object>
-                {
-                    { "test", "value" }
-                }
-            };
-            actual = JsonSerializer.Serialize(obj);
-            Assert.Equal("{\"ActualDictionary\":null,\"test\":\"value\"}", actual);
-
-            obj = new ClassWithMultipleDictionaries
-            {
-                ActualDictionary = new Dictionary<string, object>(),
-                MyOverflow = new Dictionary<string, object>
-                {
-                    { "test", "value" }
-                }
-            };
-            actual = JsonSerializer.Serialize(obj);
-            Assert.Equal("{\"ActualDictionary\":{},\"test\":\"value\"}", actual);
-        }
-
-        [Fact]
-        public static void ExtensionPropertyInvalidJsonFail()
-        {
-            const string BadJson = @"{""Good"":""OK"",""Bad"":!}";
-
-            JsonException jsonException = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(BadJson));
-            Assert.Contains("Path: $.Bad | LineNumber: 0 | BytePositionInLine: 19.", jsonException.ToString());
-            Assert.NotNull(jsonException.InnerException);
-            Assert.IsAssignableFrom<JsonException>(jsonException.InnerException);
-            Assert.Contains("!", jsonException.InnerException.ToString());
-        }
-
-        [Fact]
-        public static void ExtensionPropertyAlreadyInstantiated()
-        {
-            Assert.NotNull(new ClassWithExtensionPropertyAlreadyInstantiated().MyOverflow);
-
-            string json = @"{""MyIntMissing"":2}";
-
-            ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
-            Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
-        }
-
-        [Fact]
-        public static void ExtensionPropertyAsObject()
-        {
-            string json = @"{""MyIntMissing"":2}";
-
-            ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
-            Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
-            Assert.Equal(2, ((JsonElement)obj.MyOverflow["MyIntMissing"]).GetInt32());
-        }
-
-        [Fact]
-        public static void ExtensionPropertyCamelCasing()
-        {
-            // Currently we apply no naming policy. If we do (such as a ExtensionPropertyNamingPolicy), we'd also have to add functionality to the JsonDocument.
-
-            ClassWithExtensionProperty obj;
-            const string jsonWithProperty = @"{""MyIntMissing"":1}";
-            const string jsonWithPropertyCamelCased = @"{""myIntMissing"":1}";
-
-            {
-                // Baseline Pascal-cased json + no casing option.
-                obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonWithProperty);
-                Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
-                string json = JsonSerializer.Serialize(obj);
-                Assert.Contains(@"""MyIntMissing"":1", json);
-            }
-
-            {
-                // Pascal-cased json + camel casing option.
-                JsonSerializerOptions options = new JsonSerializerOptions();
-                options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
-                options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
-
-                obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonWithProperty, options);
-                Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
-                string json = JsonSerializer.Serialize(obj, options);
-                Assert.Contains(@"""MyIntMissing"":1", json);
-            }
-
-            {
-                // Baseline camel-cased json + no casing option.
-                obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonWithPropertyCamelCased);
-                Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
-                string json = JsonSerializer.Serialize(obj);
-                Assert.Contains(@"""myIntMissing"":1", json);
-            }
-
-            {
-                // Baseline camel-cased json + camel casing option.
-                JsonSerializerOptions options = new JsonSerializerOptions();
-                options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
-                options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
-
-                obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonWithPropertyCamelCased, options);
-                Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
-                string json = JsonSerializer.Serialize(obj, options);
-                Assert.Contains(@"""myIntMissing"":1", json);
-            }
-        }
-
-        [Fact]
-        public static void NullValuesIgnored()
-        {
-            const string json = @"{""MyNestedClass"":null}";
-            const string jsonMissing = @"{""MyNestedClassMissing"":null}";
-
-            {
-                // Baseline with no missing.
-                ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
-                Assert.Null(obj.MyOverflow);
-
-                string outJson = JsonSerializer.Serialize(obj);
-                Assert.Contains(@"""MyNestedClass"":null", outJson);
-            }
-
-            {
-                // Baseline with missing.
-                ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonMissing);
-                Assert.Equal(1, obj.MyOverflow.Count);
-                Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyNestedClassMissing"].ValueKind);
-            }
-
-            {
-                JsonSerializerOptions options = new JsonSerializerOptions();
-                options.IgnoreNullValues = true;
-
-                ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonMissing, options);
-
-                // Currently we do not ignore nulls in the extension data. The JsonDocument would also need to support this mode
-                // for any lower-level nulls.
-                Assert.Equal(1, obj.MyOverflow.Count);
-                Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyNestedClassMissing"].ValueKind);
-            }
-        }
-
-        private class ClassWithInvalidExtensionProperty
-        {
-            [JsonExtensionData]
-            public Dictionary<string, int> MyOverflow { get; set; }
-        }
-
-        private class ClassWithTwoExtensionProperties
-        {
-            [JsonExtensionData]
-            public Dictionary<string, object> MyOverflow1 { get; set; }
-
-            [JsonExtensionData]
-            public Dictionary<string, object> MyOverflow2 { get; set; }
-        }
-
-        [Fact]
-        public static void InvalidExtensionPropertyFail()
-        {
-            // Baseline
-            JsonSerializer.Deserialize<ClassWithExtensionProperty>(@"{}");
-            JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(@"{}");
-
-            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithInvalidExtensionProperty>(@"{}"));
-            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithTwoExtensionProperties>(@"{}"));
-        }
-
-        private class ClassWithIgnoredData
-        {
-            [JsonExtensionData]
-            public Dictionary<string, object> MyOverflow { get; set; }
-
-            [JsonIgnore]
-            public int MyInt { get; set; }
-        }
-
-        [Fact]
-        public static void IgnoredDataShouldNotBeExtensionData()
-        {
-            ClassWithIgnoredData obj = JsonSerializer.Deserialize<ClassWithIgnoredData>(@"{""MyInt"":1}");
-
-            Assert.Equal(0, obj.MyInt);
-            Assert.Null(obj.MyOverflow);
-        }
-
-        private class ClassWithExtensionData<T>
-        {
-            [JsonExtensionData]
-            public T Overflow { get; set; }
-        }
-
-        public class CustomOverflowDictionary<T> : Dictionary<string, T>
-        {
-        }
-
-        public class DictionaryOverflowConverter : JsonConverter<Dictionary<string, object>>
-        {
-            public override Dictionary<string, object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            {
-                throw new NotImplementedException();
-            }
-
-            public override void Write(Utf8JsonWriter writer, Dictionary<string, object> value, JsonSerializerOptions options)
-            {
-                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
-            }
-        }
-
-        public class JsonElementOverflowConverter : JsonConverter<Dictionary<string, JsonElement>>
-        {
-            public override Dictionary<string, JsonElement> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            {
-                throw new NotImplementedException();
-            }
-
-            public override void Write(Utf8JsonWriter writer, Dictionary<string, JsonElement> value, JsonSerializerOptions options)
-            {
-                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
-            }
-        }
-
-        public class JsonObjectOverflowConverter : JsonConverter<JsonObject>
-        {
-            public override JsonObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            {
-                throw new NotImplementedException();
-            }
-
-            public override void Write(Utf8JsonWriter writer, JsonObject value, JsonSerializerOptions options)
-            {
-                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
-            }
-        }
-
-        public class CustomObjectDictionaryOverflowConverter : JsonConverter<CustomOverflowDictionary<object>>
-        {
-            public override CustomOverflowDictionary<object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            {
-                throw new NotImplementedException();
-            }
-
-            public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary<object> value, JsonSerializerOptions options)
-            {
-                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
-            }
-        }
-
-        public class CustomJsonElementDictionaryOverflowConverter : JsonConverter<CustomOverflowDictionary<JsonElement>>
-        {
-            public override CustomOverflowDictionary<JsonElement> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            {
-                throw new NotImplementedException();
-            }
-
-            public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary<JsonElement> value, JsonSerializerOptions options)
-            {
-                writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
-            }
-        }
-
-        [Theory]
-        [InlineData(typeof(Dictionary<string, object>), typeof(DictionaryOverflowConverter))]
-        [InlineData(typeof(Dictionary<string, JsonElement>), typeof(JsonElementOverflowConverter))]
-        [InlineData(typeof(CustomOverflowDictionary<object>), typeof(CustomObjectDictionaryOverflowConverter))]
-        [InlineData(typeof(CustomOverflowDictionary<JsonElement>), typeof(CustomJsonElementDictionaryOverflowConverter))]
-        public static void ExtensionProperty_SupportsWritingToCustomSerializerWithOptions(Type overflowType, Type converterType)
-        {
-            typeof(ExtensionDataTests)
-                .GetMethod(nameof(ExtensionProperty_SupportsWritingToCustomSerializerWithOptionsInternal), BindingFlags.Static | BindingFlags.NonPublic)
-                .MakeGenericMethod(overflowType, converterType)
-                .Invoke(null, null);
-        }
-
-        private static void ExtensionProperty_SupportsWritingToCustomSerializerWithOptionsInternal<TDictionary, TConverter>()
-            where TDictionary : new()
-            where TConverter : JsonConverter, new()
-        {
-            var root = new ClassWithExtensionData<TDictionary>()
-            {
-                Overflow = new TDictionary()
-            };
-
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new TConverter());
-
-            string json = JsonSerializer.Serialize(root, options);
-            Assert.Equal(@"{""MyCustomOverflowWrite"":""OverflowValueWrite""}", json);
-        }
-
-        private interface IClassWithOverflow<T>
-        {
-            public T Overflow { get; set; }
-        }
-
-        private class ClassWithExtensionDataWithAttributedConverter : IClassWithOverflow<Dictionary<string, object>>
-        {
-            [JsonExtensionData]
-            [JsonConverter(typeof(DictionaryOverflowConverter))]
-            public Dictionary<string, object> Overflow { get; set; }
-        }
-
-        private class ClassWithJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow<Dictionary<string, JsonElement>>
-        {
-            [JsonExtensionData]
-            [JsonConverter(typeof(JsonElementOverflowConverter))]
-            public Dictionary<string, JsonElement> Overflow { get; set; }
-        }
-
-        private class ClassWithCustomElementExtensionDataWithAttributedConverter : IClassWithOverflow<CustomOverflowDictionary<object>>
-        {
-            [JsonExtensionData]
-            [JsonConverter(typeof(CustomObjectDictionaryOverflowConverter))]
-            public CustomOverflowDictionary<object> Overflow { get; set; }
-        }
-
-        private class ClassWithCustomJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow<CustomOverflowDictionary<JsonElement>>
-        {
-            [JsonExtensionData]
-            [JsonConverter(typeof(CustomJsonElementDictionaryOverflowConverter))]
-            public CustomOverflowDictionary<JsonElement> Overflow { get; set; }
-        }
-
-        [Theory]
-        [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary<string, object>))]
-        [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary<string, JsonElement>))]
-        [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<object>))]
-        [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<JsonElement>))]
-        public static void ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverter(Type attributedType, Type dictionaryType)
-        {
-            typeof(ExtensionDataTests)
-                .GetMethod(nameof(ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverterInternal), BindingFlags.Static | BindingFlags.NonPublic)
-                .MakeGenericMethod(attributedType, dictionaryType)
-                .Invoke(null, null);
-        }
-
-        private static void ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverterInternal<TRoot, TDictionary>()
-            where TRoot : IClassWithOverflow<TDictionary>, new()
-            where TDictionary : new()
-        {
-            var root = new TRoot()
-            {
-                Overflow = new TDictionary()
-            };
-
-            string json = JsonSerializer.Serialize(root);
-            Assert.Equal(@"{""MyCustomOverflowWrite"":""OverflowValueWrite""}", json);
-        }
-
-        [Theory]
-        [InlineData(typeof(Dictionary<string, object>), typeof(DictionaryOverflowConverter), typeof(object))]
-        [InlineData(typeof(Dictionary<string, JsonElement>), typeof(JsonElementOverflowConverter), typeof(JsonElement))]
-        [InlineData(typeof(CustomOverflowDictionary<object>), typeof(CustomObjectDictionaryOverflowConverter), typeof(object))]
-        [InlineData(typeof(CustomOverflowDictionary<JsonElement>), typeof(CustomJsonElementDictionaryOverflowConverter), typeof(JsonElement))]
-        public static void ExtensionProperty_IgnoresCustomSerializerWithOptions(Type overflowType, Type converterType, Type elementType)
-        {
-            typeof(ExtensionDataTests)
-                .GetMethod(nameof(ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal), BindingFlags.Static | BindingFlags.NonPublic)
-                .MakeGenericMethod(overflowType, elementType, converterType)
-                .Invoke(null, null);
-        }
-
-        [Fact]
-        public static void ExtensionProperty_IgnoresCustomSerializerWithOptions_JsonObject()
-        {
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonObjectOverflowConverter());
-
-            // A custom converter for JsonObject is not allowed on an extension property.
-            InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() =>
-                JsonSerializer.Deserialize<ClassWithExtensionData<JsonObject>>(@"{""TestKey"":""TestValue""}", options));
-
-            Assert.Contains("JsonObject", ex.ToString());
-        }
-
-        private static void ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal<TDictionary, TOverflowItem, TConverter>()
-            where TConverter : JsonConverter, new()
-            where TDictionary : IDictionary<string, TOverflowItem>
-        {
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new TConverter());
-
-            ClassWithExtensionData<TDictionary> obj
-                = JsonSerializer.Deserialize<ClassWithExtensionData<TDictionary>>(@"{""TestKey"":""TestValue""}", options);
-
-            Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString());
-        }
-
-        [Theory]
-        [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary<string, object>), typeof(object))]
-        [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary<string, JsonElement>), typeof(JsonElement))]
-        [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<object>), typeof(object))]
-        [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<JsonElement>), typeof(JsonElement))]
-        public static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverter(Type attributedType, Type dictionaryType, Type elementType)
-        {
-            typeof(ExtensionDataTests)
-                .GetMethod(nameof(ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal), BindingFlags.Static | BindingFlags.NonPublic)
-                .MakeGenericMethod(attributedType, dictionaryType, elementType)
-                .Invoke(null, null);
-        }
-
-        [Fact]
-        public static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverter_JsonObject()
-        {
-            ClassWithExtensionData<JsonObject> obj
-                = JsonSerializer.Deserialize<ClassWithExtensionData<JsonObject>>(@"{""TestKey"":""TestValue""}");
-
-            Assert.Equal("TestValue", obj.Overflow["TestKey"].GetValue<string>());
-        }
-
-        private static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal<TRoot, TDictionary, TOverflowItem>()
-            where TRoot : IClassWithOverflow<TDictionary>, new()
-            where TDictionary : IDictionary<string, TOverflowItem>
-        {
-            ClassWithExtensionData<TDictionary> obj
-                = JsonSerializer.Deserialize<ClassWithExtensionData<TDictionary>>(@"{""TestKey"":""TestValue""}");
-
-            Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString());
-        }
-
-        [Fact]
-        public static void ExtensionPropertyObjectValue_Empty()
-        {
-            ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
-            Assert.Equal(@"{}", JsonSerializer.Serialize(obj));
-        }
-
-        [Fact]
-        public static void ExtensionPropertyObjectValue_SameAsExtensionPropertyName()
-        {
-            const string json = @"{""MyOverflow"":{""Key1"":""V""}}";
-
-            // Deserializing directly into the overflow is not supported by design.
-            ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
-
-            // The JSON is treated as normal overflow.
-            Assert.NotNull(obj.MyOverflow["MyOverflow"]);
-            Assert.Equal(json, JsonSerializer.Serialize(obj));
-        }
-
-        private class ClassWithExtensionPropertyAsObjectAndNameProperty
-        {
-            public string Name { get; set; }
-
-            [JsonExtensionData]
-            public Dictionary<string, object> MyOverflow { get; set; }
-        }
-
-        public static IEnumerable<object[]> JsonSerializerOptions()
-        {
-            yield return new object[] { null };
-            yield return new object[] { new JsonSerializerOptions() };
-            yield return new object[] { new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement } };
-            yield return new object[] { new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode } };
-        }
-
-        [Theory]
-        [MemberData(nameof(JsonSerializerOptions))]
-        public static void ExtensionPropertyDuplicateNames(JsonSerializerOptions options)
-        {
-            var obj = new ClassWithExtensionPropertyAsObjectAndNameProperty();
-            obj.Name = "Name1";
-
-            obj.MyOverflow = new Dictionary<string, object>();
-            obj.MyOverflow["Name"] = "Name2";
-
-            string json = JsonSerializer.Serialize(obj, options);
-            Assert.Equal(@"{""Name"":""Name1"",""Name"":""Name2""}", json);
-
-            // The overflow value comes last in the JSON so it overwrites the original value.
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObjectAndNameProperty>(json, options);
-            Assert.Equal("Name2", obj.Name);
-
-            // Since there was no overflow, this should be null.
-            Assert.Null(obj.MyOverflow);
-        }
-
-        [Theory]
-        [MemberData(nameof(JsonSerializerOptions))]
-        public static void Null_SystemObject(JsonSerializerOptions options)
-        {
-            const string json = @"{""MissingProperty"":null}";
-
-            {
-                ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json, options);
-
-                // A null value maps to <object>, so the value is null.
-                object elem = obj.MyOverflow["MissingProperty"];
-                Assert.Null(elem);
-            }
-
-            {
-                ClassWithExtensionPropertyAsJsonObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonObject>(json, options);
-
-                JsonObject jObject = obj.MyOverflow;
-                JsonNode jNode = jObject["MissingProperty"];
-                // Since JsonNode is a reference type the value is null.
-                Assert.Null(jNode);
-            }
-        }
-
-        [Fact]
-        public static void Null_JsonElement()
-        {
-            const string json = @"{""MissingProperty"":null}";
-
-            ClassWithExtensionPropertyAsJsonElement obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
-            object elem = obj.MyOverflow["MissingProperty"];
-            // Since JsonElement is a struct, it treats null as JsonValueKind.Null.
-            Assert.IsType<JsonElement>(elem);
-            Assert.Equal(JsonValueKind.Null, ((JsonElement)elem).ValueKind);
-        }
-
-        [Fact]
-        public static void Null_JsonObject()
-        {
-            const string json = @"{""MissingProperty"":null}";
-
-            ClassWithExtensionPropertyAsJsonObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonObject>(json);
-            object elem = obj.MyOverflow["MissingProperty"];
-            // Since JsonNode is a reference type the value is null.
-            Assert.Null(elem);
-        }
-
-        [Fact]
-        public static void ExtensionPropertyObjectValue()
-        {
-            // Baseline
-            ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
-            obj.MyOverflow.Add("test", new object());
-            obj.MyOverflow.Add("test1", 1);
-
-            Assert.Equal(@"{""test"":{},""test1"":1}", JsonSerializer.Serialize(obj));
-        }
-
-        private class DummyObj
-        {
-            public string Prop { get; set; }
-        }
-
-        private struct DummyStruct
-        {
-            public string Prop { get; set; }
-        }
-
-        [Theory]
-        [MemberData(nameof(JsonSerializerOptions))]
-        public static void ExtensionPropertyObjectValue_RoundTrip(JsonSerializerOptions options)
-        {
-            // Baseline
-            ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}", options);
-            obj.MyOverflow.Add("test", new object());
-            obj.MyOverflow.Add("test1", 1);
-            obj.MyOverflow.Add("test2", "text");
-            obj.MyOverflow.Add("test3", new DummyObj() { Prop = "ObjectProp" });
-            obj.MyOverflow.Add("test4", new DummyStruct() { Prop = "StructProp" });
-            obj.MyOverflow.Add("test5", new Dictionary<string, object>() { { "Key", "Value" }, { "Key1", "Value1" }, });
-
-            string json = JsonSerializer.Serialize(obj);
-            ClassWithExtensionPropertyAlreadyInstantiated roundTripObj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAlreadyInstantiated>(json, options);
-
-            Assert.Equal(6, roundTripObj.MyOverflow.Count);
-
-            if (options?.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode)
-            {
-                Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test"]);
-                Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test1"]);
-                Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test2"]);
-                Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test3"]);
-
-                Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test"]);
-
-                Assert.IsAssignableFrom<JsonValue>(roundTripObj.MyOverflow["test1"]);
-                Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue<int>());
-                Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue<long>());
-
-                Assert.IsAssignableFrom<JsonValue>(roundTripObj.MyOverflow["test2"]);
-                Assert.Equal("text", ((JsonValue)roundTripObj.MyOverflow["test2"]).GetValue<string>());
-
-                Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test3"]);
-                Assert.Equal("ObjectProp", ((JsonObject)roundTripObj.MyOverflow["test3"])["Prop"].GetValue<string>());
-
-                Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test4"]);
-                Assert.Equal("StructProp", ((JsonObject)roundTripObj.MyOverflow["test4"])["Prop"].GetValue<string>());
-
-                Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test5"]);
-                Assert.Equal("Value", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key"].GetValue<string>());
-                Assert.Equal("Value1", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key1"].GetValue<string>());
-            }
-            else
-            {
-                Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test"]);
-                Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test1"]);
-                Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test2"]);
-                Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test3"]);
-
-                Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test"]).ValueKind);
-
-                Assert.Equal(JsonValueKind.Number, ((JsonElement)roundTripObj.MyOverflow["test1"]).ValueKind);
-                Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt32());
-                Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt64());
-
-                Assert.Equal(JsonValueKind.String, ((JsonElement)roundTripObj.MyOverflow["test2"]).ValueKind);
-                Assert.Equal("text", ((JsonElement)roundTripObj.MyOverflow["test2"]).GetString());
-
-                Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test3"]).ValueKind);
-                Assert.Equal("ObjectProp", ((JsonElement)roundTripObj.MyOverflow["test3"]).GetProperty("Prop").GetString());
-
-                Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test4"]).ValueKind);
-                Assert.Equal("StructProp", ((JsonElement)roundTripObj.MyOverflow["test4"]).GetProperty("Prop").GetString());
-
-                Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test5"]).ValueKind);
-                Assert.Equal("Value", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key").GetString());
-                Assert.Equal("Value1", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key1").GetString());
-            }
-        }
-
-        [Fact]
-        public static void DeserializeIntoJsonObjectProperty()
-        {
-            string json = @"{""MyDict"":{""Property1"":1}}";
-            ClassWithExtensionPropertyAsJsonObject obj =
-                JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonObject>(json);
-
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(1, obj.MyOverflow["MyDict"]["Property1"].GetValue<int>());
-        }
-
-        [Fact]
-        public static void DeserializeIntoSystemObjectProperty()
-        {
-            string json = @"{""MyDict"":{""Property1"":1}}";
-
-            Assert.Throws<InvalidOperationException>(() =>
-                JsonSerializer.Deserialize<ClassWithExtensionPropertyAsSystemObject>(json));
-
-            // Cannot deserialize into System.Object overflow even if UnknownTypeHandling is set to use JsonNode.
-            var options = new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode };
-            Assert.Throws<InvalidOperationException>(() =>
-                JsonSerializer.Deserialize<ClassWithExtensionPropertyAsSystemObject>(json));
-        }
-
-        private class ClassWithReference
-        {
-            [JsonExtensionData]
-            public Dictionary<string, JsonElement> MyOverflow { get; set; }
-
-            public ClassWithExtensionProperty MyReference { get; set; }
-        }
-
-        [Theory]
-        [InlineData(@"{""MyIntMissing"":2,""MyReference"":{""MyIntMissingChild"":3}}")]
-        [InlineData(@"{""MyReference"":{""MyIntMissingChild"":3},""MyIntMissing"":2}")]
-        [InlineData(@"{""MyReference"":{""MyNestedClass"":null,""MyInt"":0,""MyIntMissingChild"":3},""MyIntMissing"":2}")]
-        public static void NestedClass(string json)
-        {
-            ClassWithReference obj;
-
-            void Verify()
-            {
-                Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
-                Assert.Equal(1, obj.MyOverflow.Count);
-                Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
-
-                ClassWithExtensionProperty child = obj.MyReference;
-
-                Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
-                Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
-                Assert.Equal(1, child.MyOverflow.Count);
-                Assert.Equal(3, child.MyOverflow["MyIntMissingChild"].GetInt32());
-                Assert.Null(child.MyNestedClass);
-                Assert.Equal(0, child.MyInt);
-            }
-
-            obj = JsonSerializer.Deserialize<ClassWithReference>(json);
-            Verify();
-
-            // Round-trip the json and verify.
-            json = JsonSerializer.Serialize(obj);
-            obj = JsonSerializer.Deserialize<ClassWithReference>(json);
-            Verify();
-        }
-
-        private class ParentClassWithObject
-        {
-            public string Text { get; set; }
-            public ChildClassWithObject Child { get; set; }
-
-            [JsonExtensionData]
-            public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
-        }
-
-        private class ChildClassWithObject
-        {
-            public int Number { get; set; }
-
-            [JsonExtensionData]
-            public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
-        }
-
-        [Fact]
-        public static void NestedClassWithObjectExtensionDataProperty()
-        {
-            var child = new ChildClassWithObject { Number = 2 };
-            child.ExtensionData.Add("SpecialInformation", "I am child class");
-
-            var parent = new ParentClassWithObject { Text = "Hello World" };
-            parent.ExtensionData.Add("SpecialInformation", "I am parent class");
-            parent.Child = child;
-
-            // The extension data is based on the raw strings added above and not JsonElement.
-            Assert.Equal("Hello World", parent.Text);
-            Assert.IsType<string>(parent.ExtensionData["SpecialInformation"]);
-            Assert.Equal("I am parent class", (string)parent.ExtensionData["SpecialInformation"]);
-            Assert.Equal(2, parent.Child.Number);
-            Assert.IsType<string>(parent.Child.ExtensionData["SpecialInformation"]);
-            Assert.Equal("I am child class", (string)parent.Child.ExtensionData["SpecialInformation"]);
-
-            // Round-trip and verify. Extension data is now based on JsonElement.
-            string json = JsonSerializer.Serialize(parent);
-            parent = JsonSerializer.Deserialize<ParentClassWithObject>(json);
-
-            Assert.Equal("Hello World", parent.Text);
-            Assert.IsType<JsonElement>(parent.ExtensionData["SpecialInformation"]);
-            Assert.Equal("I am parent class", ((JsonElement)parent.ExtensionData["SpecialInformation"]).GetString());
-            Assert.Equal(2, parent.Child.Number);
-            Assert.IsType<JsonElement>(parent.Child.ExtensionData["SpecialInformation"]);
-            Assert.Equal("I am child class", ((JsonElement)parent.Child.ExtensionData["SpecialInformation"]).GetString());
-        }
-
-        private class ParentClassWithJsonElement
-        {
-            public string Text { get; set; }
-
-            public List<ChildClassWithJsonElement> Children { get; set; } = new List<ChildClassWithJsonElement>();
-
-            [JsonExtensionData]
-            // Use SortedDictionary as verification of supporting derived dictionaries.
-            public SortedDictionary<string, JsonElement> ExtensionData { get; set; } = new SortedDictionary<string, JsonElement>();
-        }
-
-        private class ChildClassWithJsonElement
-        {
-            public int Number { get; set; }
-
-            [JsonExtensionData]
-            public Dictionary<string, JsonElement> ExtensionData { get; set; } = new Dictionary<string, JsonElement>();
-        }
-
-        [Fact]
-        public static void NestedClassWithJsonElementExtensionDataProperty()
-        {
-            var child = new ChildClassWithJsonElement { Number = 4 };
-            child.ExtensionData.Add("SpecialInformation", JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes("I am child class")).RootElement);
-
-            var parent = new ParentClassWithJsonElement { Text = "Hello World" };
-            parent.ExtensionData.Add("SpecialInformation", JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes("I am parent class")).RootElement);
-            parent.Children.Add(child);
-
-            Verify();
-
-            // Round-trip and verify.
-            string json = JsonSerializer.Serialize(parent);
-            parent = JsonSerializer.Deserialize<ParentClassWithJsonElement>(json);
-            Verify();
-
-            void Verify()
-            {
-                Assert.Equal("Hello World", parent.Text);
-                Assert.Equal("I am parent class", parent.ExtensionData["SpecialInformation"].GetString());
-                Assert.Equal(1, parent.Children.Count);
-                Assert.Equal(4, parent.Children[0].Number);
-                Assert.Equal("I am child class", parent.Children[0].ExtensionData["SpecialInformation"].GetString());
-            }
-        }
-
-        [Fact]
-        public static void DeserializeIntoObjectProperty()
-        {
-            ClassWithExtensionPropertyAsObject obj;
-            string json;
-
-            // Baseline dictionary.
-            json = @"{""MyDict"":{""Property1"":1}}";
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyDict"]).EnumerateObject().First().Value.GetInt32());
-
-            // Attempt to deserialize directly into the overflow property; this is just added as a normal missing property like MyDict above.
-            json = @"{""MyOverflow"":{""Property1"":1}}";
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyOverflow"]).EnumerateObject().First().Value.GetInt32());
-
-            // Attempt to deserialize null into the overflow property. This is also treated as a missing property.
-            json = @"{""MyOverflow"":null}";
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Null(obj.MyOverflow["MyOverflow"]);
-
-            // Attempt to deserialize object into the overflow property. This is also treated as a missing property.
-            json = @"{""MyOverflow"":{}}";
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(JsonValueKind.Object, ((JsonElement)obj.MyOverflow["MyOverflow"]).ValueKind);
-        }
-
-        [Fact]
-        public static void DeserializeIntoMultipleDictionaries()
-        {
-            ClassWithMultipleDictionaries obj;
-            string json;
-
-            // Baseline dictionary.
-            json = @"{""ActualDictionary"":{""Key"": {""Property0"":-1}},""MyDict"":{""Property1"":1}}";
-            obj = JsonSerializer.Deserialize<ClassWithMultipleDictionaries>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyDict"]).EnumerateObject().First().Value.GetInt32());
-            Assert.Equal(1, obj.ActualDictionary.Count);
-            Assert.Equal(-1, ((JsonElement)obj.ActualDictionary["Key"]).EnumerateObject().First().Value.GetInt32());
-
-            // Attempt to deserialize null into the dictionary and overflow property. This is also treated as a missing property.
-            json = @"{""ActualDictionary"":null,""MyOverflow"":null}";
-            obj = JsonSerializer.Deserialize<ClassWithMultipleDictionaries>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Null(obj.MyOverflow["MyOverflow"]);
-            Assert.Null(obj.ActualDictionary);
-
-            // Attempt to deserialize object into the dictionary and overflow property. This is also treated as a missing property.
-            json = @"{""ActualDictionary"":{},""MyOverflow"":{}}";
-            obj = JsonSerializer.Deserialize<ClassWithMultipleDictionaries>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(JsonValueKind.Object, ((JsonElement)obj.MyOverflow["MyOverflow"]).ValueKind);
-            Assert.Equal(0, obj.ActualDictionary.Count);
-        }
-
-        [Fact]
-        public static void DeserializeIntoJsonElementProperty()
-        {
-            ClassWithExtensionPropertyAsJsonElement obj;
-            string json;
-
-            // Baseline dictionary.
-            json = @"{""MyDict"":{""Property1"":1}}";
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(1, obj.MyOverflow["MyDict"].EnumerateObject().First().Value.GetInt32());
-
-            // Attempt to deserialize directly into the overflow property; this is just added as a normal missing property like MyDict above.
-            json = @"{""MyOverflow"":{""Property1"":1}}";
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(1, obj.MyOverflow["MyOverflow"].EnumerateObject().First().Value.GetInt32());
-
-            // Attempt to deserialize null into the overflow property. This is also treated as a missing property.
-            json = @"{""MyOverflow"":null}";
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyOverflow"].ValueKind);
-
-            // Attempt to deserialize object into the overflow property. This is also treated as a missing property.
-            json = @"{""MyOverflow"":{}}";
-            obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(JsonValueKind.Object, obj.MyOverflow["MyOverflow"].ValueKind);
-        }
-
-        [Fact]
-        public static void SerializerOutputRoundtripsWhenEscaping()
-        {
-            string jsonString = "{\"\u6C49\u5B57\":\"abc\",\"Class\":{\"\u6F22\u5B57\":\"xyz\"},\"\u62DC\u6258\":{\"\u62DC\u6258\u62DC\u6258\":1}}";
-
-            ClassWithEscapedProperty input = JsonSerializer.Deserialize<ClassWithEscapedProperty>(jsonString);
-
-            Assert.Equal("abc", input.\u6C49\u5B57);
-            Assert.Equal("xyz", input.Class.\u6F22\u5B57);
-
-            string normalizedString = JsonSerializer.Serialize(input);
-
-            Assert.Equal(normalizedString, JsonSerializer.Serialize(JsonSerializer.Deserialize<ClassWithEscapedProperty>(normalizedString)));
-        }
-
-        public class ClassWithEscapedProperty
-        {
-            public string \u6C49\u5B57 { get; set; }
-            public NestedClassWithEscapedProperty Class { get; set; }
-
-            [JsonExtensionData]
-            public Dictionary<string, object> Overflow { get; set; }
-        }
-
-        public class NestedClassWithEscapedProperty
-        {
-            public string \u6F22\u5B57 { get; set; }
-        }
-
-        private class ClassWithInvalidExtensionPropertyStringString
-        {
-            [JsonExtensionData]
-            public Dictionary<string, string> MyOverflow { get; set; }
-        }
-
-        private class ClassWithInvalidExtensionPropertyObjectString
-        {
-            [JsonExtensionData]
-            public Dictionary<DummyObj, string> MyOverflow { get; set; }
-        }
-
-        private class ClassWithInvalidExtensionPropertyStringJsonNode
-        {
-            [JsonExtensionData]
-            public Dictionary<string, JsonNode> MyOverflow { get; set; }
-        }
-
-        [Fact]
-        public static void ExtensionProperty_InvalidDictionary()
-        {
-            var obj1 = new ClassWithInvalidExtensionPropertyStringString();
-            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj1));
-
-            var obj2 = new ClassWithInvalidExtensionPropertyObjectString();
-            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj2));
-
-            var obj3 = new ClassWithInvalidExtensionPropertyStringJsonNode();
-            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj3));
-        }
-
-        private class ClassWithExtensionPropertyAlreadyInstantiated
-        {
-            public ClassWithExtensionPropertyAlreadyInstantiated()
-            {
-                MyOverflow = new Dictionary<string, object>();
-            }
-
-            [JsonExtensionData]
-            public Dictionary<string, object> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyAsObject
-        {
-            [JsonExtensionData]
-            public Dictionary<string, object> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyAsJsonElement
-        {
-            [JsonExtensionData]
-            public Dictionary<string, JsonElement> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyAsJsonObject
-        {
-            [JsonExtensionData]
-            public JsonObject MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyAsSystemObject
-        {
-            [JsonExtensionData]
-            public object MyOverflow { get; set; }
-        }
-
-        private class ClassWithMultipleDictionaries
-        {
-            [JsonExtensionData]
-            public Dictionary<string, object> MyOverflow { get; set; }
-
-            public Dictionary<string, object> ActualDictionary { get; set; }
-        }
-
-        [Fact]
-        public static void DeserializeIntoImmutableDictionaryProperty()
-        {
-            // baseline
-            JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutable>(@"{}");
-            JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutableJsonElement>(@"{}");
-            JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructor>(@"{}");
-            JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructorJsonElement>(@"{}");
-
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutable>("{\"hello\":\"world\"}"));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutableJsonElement>("{\"hello\":\"world\"}"));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructor>("{\"hello\":\"world\"}"));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructorJsonElement>("{\"hello\":\"world\"}"));
-            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyCustomIImmutable>("{\"hello\":\"world\"}"));
-            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyCustomIImmutableJsonElement>("{\"hello\":\"world\"}"));
-        }
-
-        [Fact]
-        public static void SerializeIntoImmutableDictionaryProperty()
-        {
-            // attempt to serialize a null immutable dictionary
-            string expectedJson = "{}";
-            var obj = new ClassWithExtensionPropertyAsImmutable();
-            var json = JsonSerializer.Serialize(obj);
-            Assert.Equal(expectedJson, json);
-
-            // attempt to serialize an empty immutable dictionary
-            expectedJson = "{}";
-            obj = new ClassWithExtensionPropertyAsImmutable();
-            obj.MyOverflow = ImmutableDictionary<string, object>.Empty;
-            json = JsonSerializer.Serialize(obj);
-            Assert.Equal(expectedJson, json);
-
-            // attempt to serialize a populated immutable dictionary
-            expectedJson = "{\"hello\":\"world\"}";
-            obj = new ClassWithExtensionPropertyAsImmutable();
-            var dictionaryStringObject = new Dictionary<string, object> { { "hello", "world" } };
-            obj.MyOverflow = ImmutableDictionary.CreateRange(dictionaryStringObject);
-            json = JsonSerializer.Serialize(obj);
-            Assert.Equal(expectedJson, json);
-        }
-
-        private class ClassWithExtensionPropertyAsImmutable
-        {
-            [JsonExtensionData]
-            public ImmutableDictionary<string, object> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyAsImmutableJsonElement
-        {
-            [JsonExtensionData]
-            public ImmutableDictionary<string, JsonElement> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyPrivateConstructor
-        {
-            [JsonExtensionData]
-            public GenericIDictionaryWrapperPrivateConstructor<string, object> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyPrivateConstructorJsonElement
-        {
-            [JsonExtensionData]
-            public GenericIDictionaryWrapperPrivateConstructor<string, JsonElement> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyCustomIImmutable
-        {
-            [JsonExtensionData]
-            public GenericIImmutableDictionaryWrapper<string, object> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyCustomIImmutableJsonElement
-        {
-            [JsonExtensionData]
-            public GenericIImmutableDictionaryWrapper<string, JsonElement> MyOverflow { get; set; }
-        }
-
-        [Theory]
-        [InlineData(typeof(ClassWithExtensionPropertyNoGenericParameters))]
-        [InlineData(typeof(ClassWithExtensionPropertyOneGenericParameter))]
-        [InlineData(typeof(ClassWithExtensionPropertyThreeGenericParameters))]
-        public static void DeserializeIntoGenericDictionaryParameterCount(Type type)
-        {
-            object obj = JsonSerializer.Deserialize("{\"hello\":\"world\"}", type);
-
-            IDictionary<string, object> extData = (IDictionary<string, object>)type.GetProperty("MyOverflow").GetValue(obj)!;
-            Assert.Equal("world", ((JsonElement)extData["hello"]).GetString());
-        }
-
-        private class ClassWithExtensionPropertyNoGenericParameters
-        {
-            [JsonExtensionData]
-            public StringToObjectIDictionaryWrapper MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyOneGenericParameter
-        {
-            [JsonExtensionData]
-            public StringToGenericIDictionaryWrapper<object> MyOverflow { get; set; }
-        }
-
-        private class ClassWithExtensionPropertyThreeGenericParameters
-        {
-            [JsonExtensionData]
-            public GenericIDictonaryWrapperThreeGenericParameters<string, object, string> MyOverflow { get; set; }
-        }
-
-        [Fact]
-        public static void CustomObjectConverterInExtensionProperty()
-        {
-            const string Json = "{\"hello\": \"world\"}";
-
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new ObjectConverter());
-
-            ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(Json, options);
-            object overflowProp = obj.MyOverflow["hello"];
-            Assert.IsType<string>(overflowProp);
-            Assert.Equal("world!!!", ((string)overflowProp));
-
-            string newJson = JsonSerializer.Serialize(obj, options);
-            Assert.Equal("{\"hello\":\"world!!!\"}", newJson);
-        }
-
-        private class ObjectConverter : JsonConverter<object>
-        {
-            public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            {
-                return reader.GetString() + "!!!";
-            }
-
-            public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
-            {
-                // Since we are in a user-provided (not internal to S.T.Json) object converter,
-                // this converter will be called, not the internal string converter.
-                writer.WriteStringValue((string)value);
-            }
-        }
-
-        [Fact]
-        public static void CustomJsonElementConverterInExtensionProperty()
-        {
-            const string Json = "{\"hello\": \"world\"}";
-
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonElementConverter());
-
-            ClassWithExtensionPropertyAsJsonElement obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(Json, options);
-            JsonElement overflowProp = obj.MyOverflow["hello"];
-            Assert.Equal(JsonValueKind.Undefined, overflowProp.ValueKind);
-
-            string newJson = JsonSerializer.Serialize(obj, options);
-            Assert.Equal("{\"hello\":{\"Hi\":\"There\"}}", newJson);
-        }
-
-        private class JsonElementConverter : JsonConverter<JsonElement>
-        {
-            public override JsonElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            {
-                // Just return an empty JsonElement.
-                reader.Skip();
-                return new JsonElement();
-            }
-
-            public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
-            {
-                // Write a string we can test against easily.
-                writer.WriteStartObject();
-                writer.WriteString("Hi", "There");
-                writer.WriteEndObject();
-            }
-        }
-
-        [Fact]
-        public static void CustomJsonObjectConverterInExtensionProperty()
-        {
-            const string Json = "{\"hello\": \"world\"}";
-
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonObjectConverter());
-
-            // A custom converter for JsonObject is not allowed on an extension property.
-            InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() =>
-                JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonObject>(Json, options));
-
-            Assert.Contains("JsonObject", ex.ToString());
-        }
-
-        private class JsonObjectConverter : JsonConverter<JsonObject>
-        {
-            public override JsonObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            {
-                // Just return an empty JsonElement.
-                reader.Skip();
-                return new JsonObject();
-            }
-
-            public override void Write(Utf8JsonWriter writer, JsonObject value, JsonSerializerOptions options)
-            {
-                // Write a string we can test against easily.
-                writer.WriteStartObject();
-                writer.WriteString("Hi", "There");
-                writer.WriteEndObject();
-            }
-        }
-
-        [Fact]
-        public static void EmptyPropertyAndExtensionData_PropertyFirst()
-        {
-            // Verify any caching treats real property (with empty name) differently than a missing property.
-
-            ClassWithEmptyPropertyNameAndExtensionProperty obj;
-
-            // Create a new options instances to re-set any caches.
-            JsonSerializerOptions options = new JsonSerializerOptions();
-
-            // First use an empty property.
-            string json = @"{"""":43}";
-            obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
-            Assert.Equal(43, obj.MyInt1);
-            Assert.Null(obj.MyOverflow);
-
-            // Then populate cache with a missing property name.
-            json = @"{""DoesNotExist"":42}";
-            obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
-            Assert.Equal(0, obj.MyInt1);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(42, obj.MyOverflow["DoesNotExist"].GetInt32());
-        }
-
-        [Fact]
-        public static void EmptyPropertyNameAndExtensionData_ExtDataFirst()
-        {
-            // Verify any caching treats real property (with empty name) differently than a missing property.
-
-            ClassWithEmptyPropertyNameAndExtensionProperty obj;
-
-            // Create a new options instances to re-set any caches.
-            JsonSerializerOptions options = new JsonSerializerOptions();
-
-            // First populate cache with a missing property name.
-            string json = @"{""DoesNotExist"":42}";
-            obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
-            Assert.Equal(0, obj.MyInt1);
-            Assert.Equal(1, obj.MyOverflow.Count);
-            Assert.Equal(42, obj.MyOverflow["DoesNotExist"].GetInt32());
-
-            // Then use an empty property.
-            json = @"{"""":43}";
-            obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
-            Assert.Equal(43, obj.MyInt1);
-            Assert.Null(obj.MyOverflow);
-        }
-
-        [Fact]
-        public static void ExtensionDataDictionarySerialize_DoesNotHonor()
-        {
-            var options = new JsonSerializerOptions
-            {
-                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
-            };
-
-            EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize<EmptyClassWithExtensionProperty>(@"{""Key1"": 1}", options);
-
-            // Ignore naming policy for extension data properties by default.
-            Assert.False(obj.MyOverflow.ContainsKey("key1"));
-            Assert.Equal(1, obj.MyOverflow["Key1"].GetInt32());
-        }
-
-        [Theory]
-        [InlineData(0x1, 'v')]
-        [InlineData(0x1, '\u0467')]
-        [InlineData(0x10, 'v')]
-        [InlineData(0x10, '\u0467')]
-        [InlineData(0x100, 'v')]
-        [InlineData(0x100, '\u0467')]
-        [InlineData(0x1000, 'v')]
-        [InlineData(0x1000, '\u0467')]
-        [InlineData(0x10000, 'v')]
-        [InlineData(0x10000, '\u0467')]
-        public static void LongPropertyNames(int propertyLength, char ch)
-        {
-            // Although the CLR may limit member length to 1023 bytes, the serializer doesn't have a hard limit.
-
-            string val = new string(ch, propertyLength);
-            string json = @"{""" + val + @""":1}";
-
-            EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize<EmptyClassWithExtensionProperty>(json);
-
-            Assert.True(obj.MyOverflow.ContainsKey(val));
-
-            var options = new JsonSerializerOptions
-            {
-                // Avoid escaping '\u0467'.
-                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-            };
-
-            string jsonRoundTripped = JsonSerializer.Serialize(obj, options);
-            Assert.Equal(json, jsonRoundTripped);
-        }
-
-        public class EmptyClassWithExtensionProperty
-        {
-            [JsonExtensionData]
-            public IDictionary<string, JsonElement> MyOverflow { get; set; }
-        }
-
-        public class ClassWithEmptyPropertyNameAndExtensionProperty
-        {
-            [JsonPropertyName("")]
-            public int MyInt1 { get; set; }
-
-            [JsonExtensionData]
-            public IDictionary<string, JsonElement> MyOverflow { get; set; }
-        }
+        public ExtensionDataTestsDynamic() : base(JsonSerializerWrapperForString.StringSerializer) { }
     }
 }
index a5445b0..e1b1b29 100644 (file)
@@ -40,6 +40,7 @@
     <Compile Include="..\Common\ConstructorTests\ConstructorTests.Exceptions.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.Exceptions.cs" />
     <Compile Include="..\Common\ConstructorTests\ConstructorTests.ParameterMatching.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.ParameterMatching.cs" />
     <Compile Include="..\Common\ConstructorTests\ConstructorTests.Stream.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.Stream.cs" />
+    <Compile Include="..\Common\ExtensionDataTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ExtensionDataTests.cs" />
     <Compile Include="..\Common\JsonSerializerWrapperForStream.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapperForStream.cs" />
     <Compile Include="..\Common\JsonSerializerWrapperForString.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapperForString.cs" />
     <Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />