From ec3cfbf917a8a57186446e1ca46f90be0ea4310c Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 3 Sep 2021 13:34:34 -0700 Subject: [PATCH 1/2] Support JsonExtensionData in STJ source-gen --- .../gen/JsonSourceGenerator.Emitter.cs | 23 +- .../gen/JsonSourceGenerator.Parser.cs | 193 ++- .../gen/PropertyGenerationSpec.cs | 5 + .../gen/Reflection/TypeExtensions.cs | 1 + .../gen/Resources/Strings.resx | 12 + .../gen/Resources/xlf/Strings.cs.xlf | 20 + .../gen/Resources/xlf/Strings.de.xlf | 20 + .../gen/Resources/xlf/Strings.es.xlf | 20 + .../gen/Resources/xlf/Strings.fr.xlf | 20 + .../gen/Resources/xlf/Strings.it.xlf | 20 + .../gen/Resources/xlf/Strings.ja.xlf | 20 + .../gen/Resources/xlf/Strings.ko.xlf | 20 + .../gen/Resources/xlf/Strings.pl.xlf | 20 + .../gen/Resources/xlf/Strings.pt-BR.xlf | 20 + .../gen/Resources/xlf/Strings.ru.xlf | 20 + .../gen/Resources/xlf/Strings.tr.xlf | 20 + .../gen/Resources/xlf/Strings.zh-Hans.xlf | 20 + .../gen/Resources/xlf/Strings.zh-Hant.xlf | 20 + .../gen/TypeGenerationSpec.cs | 13 + .../System.Text.Json/ref/System.Text.Json.cs | 3 +- .../JsonMetadataServicesConverter.cs | 2 +- .../Json/Serialization/JsonConverterOfT.cs | 6 +- .../JsonMetadataServices.Converters.cs | 7 + .../Metadata/JsonMetadataServices.cs | 4 +- .../Metadata/JsonPropertyInfo.cs | 5 + .../Metadata/JsonPropertyInfoOfT.cs | 2 + .../Metadata/JsonTypeInfo.Cache.cs | 10 + .../Serialization/Metadata/JsonTypeInfo.cs | 26 +- .../Text/Json/ThrowHelper.Serialization.cs | 4 +- .../ConstructorTests.Cache.cs | 27 +- .../ConstructorTests.Exceptions.cs | 15 +- .../ConstructorTests.ParameterMatching.cs | 30 +- .../ConstructorTests.Stream.cs | 13 +- .../tests/Common/ExtensionDataTests.cs | 1475 +++++++++++++++++ .../TestClasses/TestClasses.Constructor.cs | 36 +- .../Serialization/ConstructorTests.cs | 8 +- .../Serialization/ExtensionDataTests.cs | 131 ++ ...em.Text.Json.SourceGeneration.Tests.csproj | 2 + .../Serialization/ExtensionDataTests.cs | 1439 +--------------- .../System.Text.Json.Tests.csproj | 1 + 40 files changed, 2168 insertions(+), 1585 deletions(-) create mode 100644 src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ExtensionDataTests.cs diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 2f9a64f12973f8..8ce170b7002ac9 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -48,7 +48,6 @@ private sealed partial class Emitter 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 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) GenerateTypeInfo(spec.TypeGenerationSpec); } } + + TypeGenerationSpec? extPropTypeSpec = typeGenerationSpec.ExtensionDataPropertyTypeSpec; + if (extPropTypeSpec != null) + { + GenerateTypeInfo(extPropTypeSpec); + } } break; case ClassType.KnownUnsupportedType: @@ -431,9 +436,18 @@ private string GenerateForCollection(TypeGenerationSpec typeGenerationSpec) 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 string GeneratePropMetadataInitFunc(TypeGenerationSpec typeGenerationSpe {setterNamedArg}, {ignoreConditionNamedArg}, hasJsonInclude: {ToCSharpKeyword(memberMetadata.HasJsonInclude)}, + isExtensionData: {ToCSharpKeyword(memberMetadata.IsExtensionData)}, numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)}, propertyName: ""{clrPropertyName}"", {jsonPropertyNameNamedArg}); diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 199c0db68e12d1..2ad36dd32f192d 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -24,8 +24,10 @@ private sealed class Parser { 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 @@ private sealed class Parser 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 @@ private sealed class Parser 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 @@ private sealed class Parser 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 @@ public Parser(Compilation compilation, in SourceProductionContext sourceProducti _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 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener _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? propGenSpecList = null; ObjectConstructionStrategy constructionStrategy = default; ParameterGenerationSpec[]? paramGenSpecArray = null; @@ -653,6 +677,9 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } Type actualTypeToConvert; + Type? keyType = null; + Type valueType; + bool needsRuntimeType = false; if (type.IsArray) { @@ -660,13 +687,13 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener ? 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 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener 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 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener 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 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener 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 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener 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 @@ void CacheMemberHelper() // 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 @@ void CacheMemberHelper() 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 @@ private bool ImplementsIAsyncEnumerableInterface(Type type) 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 @@ private static bool PropertyIsOverridenAndIgnored( 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 attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo); - ProcessCustomAttributes( + ProcessMemberCustomAttributes( attributeDataList, memberCLRType, out bool hasJsonInclude, @@ -980,7 +1067,8 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, out JsonNumberHandling? numberHandling, out string? converterInstantiationLogic, out int order, - out bool hasFactoryConverter); + out bool hasFactoryConverter, + out bool isExtensionData); ProcessMember( memberInfo, @@ -1015,6 +1103,7 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, NumberHandling = numberHandling, Order = order, HasJsonInclude = hasJsonInclude, + IsExtensionData = isExtensionData, TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode), DeclaringTypeRef = memberInfo.DeclaringType.GetCompilableName(), ConverterInstantiationLogic = converterInstantiationLogic, @@ -1037,7 +1126,7 @@ private Type GetMemberClrType(MemberInfo memberInfo) throw new InvalidOperationException(); } - private void ProcessCustomAttributes( + private void ProcessMemberCustomAttributes( IList attributeDataList, Type memberCLRType, out bool hasJsonInclude, @@ -1046,7 +1135,8 @@ private void ProcessCustomAttributes( 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 @@ private void ProcessCustomAttributes( numberHandling = default; converterInstantiationLogic = null; order = 0; + isExtensionData = false; bool foundDesignTimeCustomConverter = false; hasFactoryConverter = false; @@ -1115,6 +1206,11 @@ private void ProcessCustomAttributes( order = (int)ctorArgs[0].Value; } break; + case JsonExtensionDataAttributeFullName: + { + isExtensionData = true; + } + break; default: break; } @@ -1315,30 +1411,33 @@ private void PopulateKnownTypes() _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 types, Type? type) { - _knownUnsupportedTypes.Add(_timeOnlyType); + if (type != null) + { + types.Add(type); + } } } } diff --git a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs index 886381a22a8bef..50400453971574 100644 --- a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs @@ -75,6 +75,11 @@ internal sealed class PropertyGenerationSpec /// public bool HasJsonInclude { get; init; } + /// + /// Whether the property has the JsonExtensionDataAttribute. + /// + public bool IsExtensionData { get; init; } + /// /// Generation specification for the property's type. /// diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs index ba20339565cbf9..90e3333a929db1 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text.Json.SourceGeneration; namespace System.Text.Json.Reflection { diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index 5e60f5e377c5d6..3b877ae96ab239 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -141,4 +141,16 @@ Type has multiple constructors annotated with JsonConstructorAttribute. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + Type has multiple members annotated with JsonExtensionDataAttribute. + + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + Data extension property type invalid. + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf index 4bde155b650b24..a038312d189786 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -12,6 +12,16 @@ Odvozené typy JsonSerializerContext a všechny obsahující typy musí být částečné. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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. @@ -32,6 +42,16 @@ Typ obsahuje více konstruktorů anotovaných s JsonConstructorAttribute. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. Nevygenerovala se metadata serializace pro typ {0}. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf index 093d8a6cdb4715..360e9e5be253c7 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -12,6 +12,16 @@ Abgeleitete JsonSerializerContext-Typen und alle enthaltenden Typen müssen partiell sein. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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. @@ -32,6 +42,16 @@ Der Typ weist mehrere Konstruktoren mit dem Kommentar JsonConstructorAttribute auf. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. Die Serialisierungsmetadaten für den Typ "{0}" wurden nicht generiert. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf index ed12d993f5c5a6..091631a9c2fe07 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -12,6 +12,16 @@ Los tipos derivados "JsonSerializerContext" y todos los tipos que contienen deben ser parciales. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. Hay varios tipos denominados {0}. El origen se generó para el primero detectado. Use "JsonSerializableAttribute.TypeInfoPropertyName" para resolver esta colisión. @@ -32,6 +42,16 @@ El tipo tiene varios constructores anotados con JsonConstructorAttribute. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. No generó metadatos de serialización para el tipo '{0}". diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf index ddea910bd05ba9..9d8a78e10d74f0 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -12,6 +12,16 @@ Les types dérivés 'JsonSerializerContext' et tous les types conteneurs doivent être partiels. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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. @@ -32,6 +42,16 @@ Le type a plusieurs constructeurs annotés avec JsonConstructorAttribute. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. Les métadonnées de sérialisation pour le type « {0} » n’ont pas été générées. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf index d26aba1c84f9b9..fc80dbedab2bf3 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -12,6 +12,16 @@ I tipi derivati 'JsonSerializerContext' e tutti i tipi contenenti devono essere parziali. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. Sono presenti più tipi denominati {0}. L'origine è stata generata per il primo tipo rilevato. Per risolvere questa collisione, usare 'JsonSerializableAttribute.TypeInfoPropertyName'. @@ -32,6 +42,16 @@ Il tipo contiene più costruttori che presentano l'annotazione JsonConstructorAttribute. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. Non sono stati generati metadati di serializzazione per il tipo '{0}'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf index 50270d6c4d401d..913d6c8edc9939 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -12,6 +12,16 @@ 派生した 'JsonSerializerContext' 型と含まれているすべての型は部分的である必要があります。 + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. {0} と名前が付けられた種類が複数あります。最初に検出されたものに対してソースが生成されました。この問題を解決するには、'JsonSerializableAttribute.TypeInfoPropertyName' を使用します。 @@ -32,6 +42,16 @@ 型には、JsonConstructorAttribute で注釈が付けられた複数のコンストラクターがあります。 + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. '{0}'型 のシリアル化メタデータを生成ませんでした。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf index a1fd856af18575..4c79cc3bc7e099 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -12,6 +12,16 @@ 파생된 'JsonSerializerContext' 형식과 포함하는 모든 형식은 부분이어야 합니다. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 이름이 {0}인 형식이 여러 개 있습니다. 처음 검색한 원본에 대해 원본이 생성되었습니다. 이 충돌을 해결하려면 'JsonSerializableAttribute.TypeInfoPropertyName'을 사용하세요. @@ -32,6 +42,16 @@ 해당 형식에 JsonConstructorAttribute로 주석이 추가된 여러 생성자가 있습니다. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. '{0}' 형식에 대한 직렬화 메타데이터가 생성되지 않았습니다. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf index e04db80de054be..2dc1b07a99c8c8 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -12,6 +12,16 @@ Pochodne typy "JsonSerializerContext" i wszystkich zawierające typy muszą być częściowe. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. Istnieje wiele typów o nazwie {0}. Wygenerowano źródło dla pierwszego wykrytego elementu. Aby rozwiązać tę kolizję, użyj „JsonSerializableAttribute. TypeInfoPropertyName”. @@ -32,6 +42,16 @@ Typ ma wiele konstruktorów z adnotacją JsonConstructorAttribute. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. Nie wygenerowano metadanych serializacji dla typu „{0}”. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf index bd08c889d012fd..4576c53882eaab 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -12,6 +12,16 @@ Os tipos derivados de 'JsonSerializerContext' e todos os tipos contidos devem ser parciais. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. Existem vários tipos chamados {0}. A fonte foi gerada para o primeiro detectado. Use 'JsonSerializableAttribute.TypeInfoPropertyName' para resolver esta colisão. @@ -32,6 +42,16 @@ O tipo tem vários construtores anotados com JsonConstructorAttribute. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. Não gerou metadados de serialização para o tipo '{0}'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf index fdd7824a1f54eb..b321e422517e66 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -12,6 +12,16 @@ Производные типы "JsonSerializerContext" и все содержащие типы должны быть частичными. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. Существует несколько типов с именем {0}. Исходный код сформирован для первого обнаруженного типа. Используйте JsonSerializableAttribute.TypeInfoPropertyName для устранения этого конфликта. @@ -32,6 +42,16 @@ Тип имеет несколько конструкторов, аннотированных с использованием JsonConstructorAttribute. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. Метаданные сериализации для типа "{0}" не сформированы. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf index 7844892dcbb498..499dbe33b8e369 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -12,6 +12,16 @@ Türetilmiş 'JsonSerializerContext' türleri ve bunları içeren tüm türler kısmi olmalıdır. + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. {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. @@ -32,6 +42,16 @@ Türün JsonConstructorAttribute ile açıklanan birden çok oluşturucusu var. + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. '{0}' türü için serileştirme meta verileri oluşturulmadı. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf index 10e68c87ede68d..fd4103a4505f9e 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -12,6 +12,16 @@ 派生的 “JsonSerializerContext” 类型以及所有包含类型必须是部分。 + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 有多个名为 {0} 的类型。已为第一个检测到类型的生成源。请使用 'JsonSerializableAttribute.TypeInfoPropertyName' 以解决此冲突。 @@ -32,6 +42,16 @@ 类型具有用 JsonConstructorAttribute 批注的多个构造函数。 + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. 未生成类型 '{0}' 的序列化元数据。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf index 2b5a59991a12a7..0d8bc1ae08c64a 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -12,6 +12,16 @@ 衍生的 'JsonSerializerContext' 類型和所有包含類型必須是部份。 + + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + + + + Data extension property type invalid. + Data extension property type invalid. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 有多個名為 {0} 的類型。已為偵測到的第一個項目產生來源。請使用 'JsonSerializableAttribute.TypeInfoPropertyName' 解決此衝突。 @@ -32,6 +42,16 @@ 類型包含多個以 JsonConstructorAttribute 註解的建構函式。 + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Type has multiple members annotated with JsonExtensionDataAttribute. + + Did not generate serialization metadata for type '{0}'. 未產生類型 '{0}' 的序列化中繼資料。 diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index be51f949d4fbd5..b61d7810a227f6 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -58,6 +58,10 @@ internal class TypeGenerationSpec public TypeGenerationSpec? NullableUnderlyingTypeMetadata { get; private set; } + 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 +113,8 @@ public void Initialize( TypeGenerationSpec? collectionValueTypeMetadata, ObjectConstructionStrategy constructionStrategy, TypeGenerationSpec? nullableUnderlyingTypeMetadata, + string? runtimeTypeRef, + TypeGenerationSpec? extensionDataPropertyTypeSpec, string? converterInstantiationLogic, bool implementsIJsonOnSerialized, bool implementsIJsonOnSerializing, @@ -130,6 +136,8 @@ public void Initialize( CollectionValueTypeMetadata = collectionValueTypeMetadata; ConstructionStrategy = constructionStrategy; NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata; + RuntimeTypeRef = runtimeTypeRef; + ExtensionDataPropertyTypeSpec = extensionDataPropertyTypeSpec; ConverterInstantiationLogic = converterInstantiationLogic; ImplementsIJsonOnSerialized = implementsIJsonOnSerialized; ImplementsIJsonOnSerializing = implementsIJsonOnSerializing; @@ -221,6 +229,11 @@ private bool FastPathIsSupported() { if (ClassType == ClassType.Object) { + if (ExtensionDataPropertyTypeSpec != null) + { + return false; + } + foreach (PropertyGenerationSpec property in PropertyGenSpecList) { if (property.TypeGenerationSpec.Type.IsObjectType() || diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 09340a208c9cee..23b632240cabe5 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -963,6 +963,7 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.JsonConverter Int32Converter { get { throw null; } } public static System.Text.Json.Serialization.JsonConverter Int64Converter { get { throw null; } } public static System.Text.Json.Serialization.JsonConverter JsonElementConverter { get { throw null; } } + public static System.Text.Json.Serialization.JsonConverter JsonObjectConverter { get { throw null; } } public static System.Text.Json.Serialization.JsonConverter ObjectConverter { get { throw null; } } [System.CLSCompliantAttribute(false)] public static System.Text.Json.Serialization.JsonConverter SByteConverter { get { throw null; } } @@ -994,7 +995,7 @@ public static partial class JsonMetadataServices public static JsonTypeInfo CreateISetInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.ISet { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateListInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.List { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonObjectInfoValues objectInfo) where T : notnull { throw null; } - public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(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? converter, System.Func? getter, System.Action? 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(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? converter, System.Func? getter, System.Action? 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 CreateQueueInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Queue { throw null; } public static JsonTypeInfo CreateStackInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Stack { throw null; } public static JsonTypeInfo CreateStackOrQueueInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc, System.Action addFunc) where TCollection : System.Collections.IEnumerable { throw null; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs index 88b93f95d1c487..9bc2f3b2ba9b1c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs @@ -22,7 +22,7 @@ internal sealed class JsonMetadataServicesConverter : JsonResumableConverter< private JsonConverter? _converter; // A backing converter for when fast-path logic cannot be used. - private JsonConverter Converter + internal JsonConverter Converter { get { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs index 497c380c8111cc..3deefaf77a6edc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs @@ -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 @@ internal bool TryWriteDataExtensionProperty(Utf8JsonWriter writer, T value, Json return TryWrite(writer, value, options, ref state); } - if (this is not JsonDictionaryConverter dictionaryConverter) + JsonDictionaryConverter? dictionaryConverter = this as JsonDictionaryConverter + ?? (this as JsonMetadataServicesConverter)?.Converter as JsonDictionaryConverter; + + if (dictionaryConverter == null) { // If not JsonDictionaryConverter then we are JsonObject. // Avoid a type reference to JsonObject and its converter to support trimming. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs index 2f3b8e79d3c785..20d5b1f7ed1fbe 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs @@ -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 @@ -86,6 +87,12 @@ public static partial class JsonMetadataServices public static JsonConverter JsonElementConverter => s_jsonElementConverter ??= new JsonElementConverter(); private static JsonConverter? s_jsonElementConverter; + /// + /// Returns a instance that converts values. + /// + public static JsonConverter JsonObjectConverter => s_jsonObjectConverter ??= new JsonObjectConverter(); + private static JsonConverter? s_jsonObjectConverter; + /// /// Returns a instance that converts values. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs index 20b33ac2e28761..f7cd608313407a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs @@ -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 @@ public static partial class JsonMetadataServices /// Specifies a condition for the property to be ignored. /// If the property or field is a number, specifies how it should processed when serializing and deserializing. /// Whether the property was annotated with . + /// Whether the property was annotated with . /// The CLR name of the property or field. /// The name to be used when processing the property or field, specified by . /// A instance intialized with the provided metadata. @@ -43,6 +43,7 @@ public static JsonPropertyInfo CreatePropertyInfo( Action? setter, JsonIgnoreCondition? ignoreCondition, bool hasJsonInclude, + bool isExtensionData, JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) @@ -93,6 +94,7 @@ public static JsonPropertyInfo CreatePropertyInfo( setter, ignoreCondition, hasJsonInclude, + isExtensionData, numberHandling, propertyName, jsonPropertyName); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index 0ae3d8126d931c..3b8c003ac3066c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -503,6 +503,11 @@ internal JsonTypeInfo RuntimeTypeInfo /// internal bool SrcGen_HasJsonInclude { get; set; } + /// + /// Relevant to source generated metadata: did the property have the ? + /// + internal bool SrcGen_IsExtensionData { get; set; } + /// /// Relevant to source generated metadata: is the property public? /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index 6111f46117c347..71363ca678c4e6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -127,6 +127,7 @@ internal void InitializeForSourceGen( Action? setter, JsonIgnoreCondition? ignoreCondition, bool hasJsonInclude, + bool isExtensionData, JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) @@ -157,6 +158,7 @@ internal void InitializeForSourceGen( SrcGen_IsPublic = isPublic; SrcGen_HasJsonInclude = hasJsonInclude; + SrcGen_IsExtensionData = isExtensionData; DeclaredPropertyType = typeof(T); ConverterBase = converter; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs index 00911d756bdefb..d3f2c3c9bee6f1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs @@ -613,6 +613,16 @@ internal void InitializePropCache() 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); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index 28fb27669139e6..cafba2fe577e98 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -504,7 +504,7 @@ private void InitializeConstructorParameters(JsonParameterInfoValues[] jsonParam 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 @@ private static bool PropertyIsOverridenAndIgnored( private void ValidateAndAssignDataExtensionProperty(JsonPropertyInfo jsonPropertyInfo) { - Type memberType = jsonPropertyInfo.DeclaredPropertyType; - JsonConverter? converter = null; - if (typeof(IDictionary).IsAssignableFrom(memberType) || - typeof(IDictionary).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 @@ private void ValidateAndAssignDataExtensionProperty(JsonPropertyInfo jsonPropert DataExtensionProperty = jsonPropertyInfo; } + private bool IsValidDataExtensionProperty(JsonPropertyInfo jsonPropertyInfo) + { + Type memberType = jsonPropertyInfo.DeclaredPropertyType; + + bool typeIsValid = typeof(IDictionary).IsAssignableFrom(memberType) || + typeof(IDictionary).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, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 9de23e8d011a9e..a75f5eaa484067 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -215,9 +215,9 @@ public static void ThrowInvalidOperationException_ConstructorParameterIncomplete [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] diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs index e2275bc7ff7f8f..63c5398f901429 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs @@ -11,9 +11,6 @@ public abstract partial class ConstructorTests { [Fact] [OuterLoop] -#if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Needs JsonExtensionData support.")] -#endif public async Task MultipleThreadsLooping() { const int Iterations = 100; @@ -25,13 +22,10 @@ public async Task MultipleThreadsLooping() } [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 @@ async Task DeserializeObjectNormalAsync(Type type, JsonSerializerOptions options 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 @@ async Task RunTestAsync(Type type) 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 @@ public async Task PropertyCacheWithMinInputsFirst() var options = new JsonSerializerOptions(); string json = "{}"; - await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + await JsonSerializerWrapperForString.DeserializeWrapper(json, options); - ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance(); + ObjWCtorMixedParams testObj = ObjWCtorMixedParams.GetInstance(); testObj.Verify(); json = await JsonSerializerWrapperForString.SerializeWrapper(testObj, options); - testObj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + testObj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); testObj.Verify(); } @@ -115,15 +109,15 @@ public async Task PropertyCacheWithMinInputsLast() // 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(json, options); + testObj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); testObj.Verify(); json = "{}"; - await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + await JsonSerializerWrapperForString.DeserializeWrapper(json, options); } // Use a common options instance to encourage additional metadata collisions across types. Also since @@ -132,9 +126,6 @@ public async Task PropertyCacheWithMinInputsLast() [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(object[] args) diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs index 83d6f7000e175f..4e07bf6bb6b3ad 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs @@ -114,14 +114,11 @@ public async Task RandomReferenceMetadataNotSupported() } [Fact] -#if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Needs JsonExtensionData support.")] -#endif public async Task ExtensionDataProperty_CannotBindTo_CtorParam() { InvalidOperationException ex = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{}")); - 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); } @@ -300,21 +297,21 @@ public async Task CaseInsensitiveFails() // Baseline (no exception) { - var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""mydecimal"":1}", options); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""mydecimal"":1}", options); Assert.Equal(1, obj.MyDecimal); } { - var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MYDECIMAL"":1}", options); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MYDECIMAL"":1}", options); Assert.Equal(1, obj.MyDecimal); } JsonException e; - e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""mydecimal"":bad}", options)); + e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""mydecimal"":bad}", options)); Assert.Equal("$.mydecimal", e.Path); - e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""MYDECIMAL"":bad}", options)); + e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""MYDECIMAL"":bad}", options)); Assert.Equal("$.MYDECIMAL", e.Path); } diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs index 5f2da8c2caa8cd..c04815dc85e6a1 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs @@ -291,9 +291,6 @@ public async Task Null_AsArgument_To_ParameterThat_CanNotBeNull() } [Fact] -#if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Needs JsonExtensionData support.")] -#endif public async Task OtherPropertiesAreSet() { var personClass = await JsonSerializerWrapperForString.DeserializeWrapper(Person_Class.s_json); @@ -312,9 +309,6 @@ public async Task ExtraProperties_AreIgnored() } [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(@"{""X"":1,""y"":2,""b"":3}"); @@ -368,7 +362,7 @@ public async Task IgnoreNullValues_DontSetNull_ToConstructorArguments_ThatCantBe [Fact] public async Task NumerousSimpleAndComplexParameters() { - var obj = await JsonSerializerWrapperForString.DeserializeWrapper(ClassWithConstructor_SimpleAndComplexParameters.s_json); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(ObjWCtorMixedParams.s_json); obj.Verify(); } @@ -565,7 +559,7 @@ public async Task TupleDeserialization_DefaultValuesUsed_WhenJsonMissing() #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 @@ public async Task TupleDeserializationWorks_ClassWithParameterizedCtor() string complexTupleJson = sb.ToString(); var complexTuple = await JsonSerializerWrapperForString.DeserializeWrapper>(complexTupleJson); + ObjWCtorMixedParams, + ObjWCtorMixedParams, + ObjWCtorMixedParams, + ObjWCtorMixedParams, + ObjWCtorMixedParams, + ObjWCtorMixedParams, + ObjWCtorMixedParams>>(complexTupleJson); complexTuple.Item1.Verify(); complexTuple.Item2.Verify(); @@ -793,9 +787,6 @@ public async Task LastParameterWins() } [Fact] -#if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Needs JsonExtensionData support.")] -#endif public async Task LastParameterWins_DoesNotGoToExtensionData() { string json = @"{ @@ -823,9 +814,6 @@ public async Task BitVector32_UsesStructDefaultCtor_MultipleParameterizedCtor() } [Fact] -#if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Needs JsonExtensionData support.")] -#endif public async Task HonorExtensionDataGeneric() { var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""key"": ""value""}"); diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Stream.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Stream.cs index ac5cd60933bf9f..92b3b480506df0 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Stream.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Stream.cs @@ -12,9 +12,6 @@ public abstract partial class ConstructorTests { [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(byte[] testData) @@ -37,7 +34,7 @@ async Task RunTestAsync(byte[] testData) // Simple models can be deserialized. tasks[0] = Task.Run(async () => await RunTestAsync(Parameterized_IndexViewModel_Immutable.s_data)); // Complex models can be deserialized. - tasks[1] = Task.Run(async () => await RunTestAsync(ClassWithConstructor_SimpleAndComplexParameters.s_data)); + tasks[1] = Task.Run(async () => await RunTestAsync(ObjWCtorMixedParams.s_data)); tasks[2] = Task.Run(async () => await RunTestAsync(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.s_data)); @@ -62,9 +59,6 @@ async Task RunTestAsync(byte[] testData) } [Fact] -#if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Needs JsonExtensionData support.")] -#endif public async Task ReadSimpleObjectWithTrailingTriviaAsync() { async Task RunTestAsync(string testData) @@ -89,7 +83,7 @@ async Task RunTestAsync(string testData) // Simple models can be deserialized. tasks[0] = Task.Run(async () => await RunTestAsync(Parameterized_IndexViewModel_Immutable.s_json)); // Complex models can be deserialized. - tasks[1] = Task.Run(async () => await RunTestAsync(ClassWithConstructor_SimpleAndComplexParameters.s_json)); + tasks[1] = Task.Run(async () => await RunTestAsync(ObjWCtorMixedParams.s_json)); tasks[2] = Task.Run(async () => await RunTestAsync(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.s_json)); @@ -157,9 +151,6 @@ async Task RunTestAsync() } [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 index 00000000000000..0c4724d5e293cb --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs @@ -0,0 +1,1475 @@ +// 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(json, options); + Assert.Equal(1, obj.MyInt1); + Assert.Null(obj.MyOverflow); + } + + [Fact] + public async Task EmptyPropertyNameInExtensionData() + { + { + string json = @"{"""":42}"; + EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper(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(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(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(json); + Verify(); + } + + // Round-trip the json. + { + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(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() + { + Diagnostics.Debugger.Launch(); + string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}"; + ClassWithExtensionField obj = await JsonSerializerWrapperForString.DeserializeWrapper(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(json); + Verify(); + } + + // Round-trip the json. + { + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(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() + }; + actual = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal("{\"ActualDictionary\":{}}", actual); + + obj = new ClassWithMultipleDictionaries + { + MyOverflow = new Dictionary + { + { "test", "value" } + } + }; + actual = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal("{\"ActualDictionary\":null,\"test\":\"value\"}", actual); + + obj = new ClassWithMultipleDictionaries + { + ActualDictionary = new Dictionary(), + MyOverflow = new Dictionary + { + { "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(async () => await JsonSerializerWrapperForString.DeserializeWrapper(BadJson)); + Assert.Contains("Path: $.Bad | LineNumber: 0 | BytePositionInLine: 19.", jsonException.ToString()); + Assert.NotNull(jsonException.InnerException); + Assert.IsAssignableFrom(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(json); + Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32()); + } + + [Fact] + public async Task ExtensionPropertyAsObject() + { + string json = @"{""MyIntMissing"":2}"; + + ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + Assert.IsType(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(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(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(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(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(json); + Assert.Null(obj.MyOverflow); + + string outJson = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Contains(@"""MyNestedClass"":null", outJson); + } + + { + // Baseline with missing. + ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper(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(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 MyOverflow { get; set; } + } + + public class ClassWithTwoExtensionProperties + { + [JsonExtensionData] + public Dictionary MyOverflow1 { get; set; } + + [JsonExtensionData] + public Dictionary MyOverflow2 { get; set; } + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Compile-time errors thrown for these scenarios.")] +#endif + public async Task InvalidExtensionPropertyFail() + { + // Baseline + await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); + await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); + + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(@"{}")); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(@"{}")); + } + + public class ClassWithIgnoredData + { + [JsonExtensionData] + public Dictionary MyOverflow { get; set; } + + [JsonIgnore] + public int MyInt { get; set; } + } + + [Fact] + public async Task IgnoredDataShouldNotBeExtensionData() + { + ClassWithIgnoredData obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1}"); + + Assert.Equal(0, obj.MyInt); + Assert.Null(obj.MyOverflow); + } + + public class ClassWithExtensionData + { + [JsonExtensionData] + public T Overflow { get; set; } + } + + public class CustomOverflowDictionary : Dictionary + { + } + + public class DictionaryOverflowConverter : JsonConverter> + { + public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite"); + } + } + + public class JsonElementOverflowConverter : JsonConverter> + { + public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite"); + } + } + + public class JsonObjectOverflowConverter : JsonConverter + { + 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> + { + public override CustomOverflowDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary value, JsonSerializerOptions options) + { + writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite"); + } + } + + public class CustomJsonElementDictionaryOverflowConverter : JsonConverter> + { + public override CustomOverflowDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary value, JsonSerializerOptions options) + { + writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite"); + } + } + + [Theory] + [InlineData(typeof(Dictionary), typeof(DictionaryOverflowConverter))] + [InlineData(typeof(Dictionary), typeof(JsonElementOverflowConverter))] + [InlineData(typeof(CustomOverflowDictionary), typeof(CustomObjectDictionaryOverflowConverter))] + [InlineData(typeof(CustomOverflowDictionary), 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() + where TDictionary : new() + where TConverter : JsonConverter, new() + { + var root = new ClassWithExtensionData() + { + 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 + { + public T Overflow { get; set; } + } + + public class ClassWithExtensionDataWithAttributedConverter : IClassWithOverflow> + { + [JsonExtensionData] + [JsonConverter(typeof(DictionaryOverflowConverter))] + public Dictionary Overflow { get; set; } + } + + public class ClassWithJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow> + { + [JsonExtensionData] + [JsonConverter(typeof(JsonElementOverflowConverter))] + public Dictionary Overflow { get; set; } + } + + public class ClassWithCustomElementExtensionDataWithAttributedConverter : IClassWithOverflow> + { + [JsonExtensionData] + [JsonConverter(typeof(CustomObjectDictionaryOverflowConverter))] + public CustomOverflowDictionary Overflow { get; set; } + } + + public class ClassWithCustomJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow> + { + [JsonExtensionData] + [JsonConverter(typeof(CustomJsonElementDictionaryOverflowConverter))] + public CustomOverflowDictionary Overflow { get; set; } + } + + [Theory] + [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary))] + [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary))] + [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary))] + [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary))] + 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() + where TRoot : IClassWithOverflow, 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), typeof(DictionaryOverflowConverter), typeof(object))] + [InlineData(typeof(Dictionary), typeof(JsonElementOverflowConverter), typeof(JsonElement))] + [InlineData(typeof(CustomOverflowDictionary), typeof(CustomObjectDictionaryOverflowConverter), typeof(object))] + [InlineData(typeof(CustomOverflowDictionary), 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(async () => + await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""TestKey"":""TestValue""}", options)); + + Assert.Contains("JsonObject", ex.ToString()); + } + + private static void ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal() + where TConverter : JsonConverter, new() + where TDictionary : IDictionary + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new TConverter()); + + ClassWithExtensionData obj + = JsonSerializer.Deserialize>(@"{""TestKey"":""TestValue""}", options); + + Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString()); + } + + [Theory] + [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary), typeof(object))] + [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary), typeof(JsonElement))] + [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary), typeof(object))] + [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary), 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 obj + = await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""TestKey"":""TestValue""}"); + + Assert.Equal("TestValue", obj.Overflow["TestKey"].GetValue()); + } + + private static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal() + where TRoot : IClassWithOverflow, new() + where TDictionary : IDictionary + { + ClassWithExtensionData obj + = JsonSerializer.Deserialize>(@"{""TestKey"":""TestValue""}"); + + Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString()); + } + + [Fact] + public async Task ExtensionPropertyObjectValue_Empty() + { + ClassWithExtensionPropertyAlreadyInstantiated obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); + 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(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 MyOverflow { get; set; } + } + + public static IEnumerable 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(); + 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(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(json, options); + + // A null value maps to , so the value is null. + object elem = obj.MyOverflow["MissingProperty"]; + Assert.Null(elem); + } + + { + ClassWithExtensionPropertyAsJsonObject obj = await JsonSerializerWrapperForString.DeserializeWrapper(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(json); + object elem = obj.MyOverflow["MissingProperty"]; + // Since JsonElement is a struct, it treats null as JsonValueKind.Null. + Assert.IsType(elem); + Assert.Equal(JsonValueKind.Null, ((JsonElement)elem).ValueKind); + } + + [Fact] + public async Task Null_JsonObject() + { + const string json = @"{""MissingProperty"":null}"; + + ClassWithExtensionPropertyAsJsonObject obj = await JsonSerializerWrapperForString.DeserializeWrapper(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(@"{}"); + 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(@"{}", 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() { { "Key", "Value" }, { "Key1", "Value1" }, }); + + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + ClassWithExtensionPropertyAlreadyInstantiated roundTripObj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + Assert.Equal(6, roundTripObj.MyOverflow.Count); + + if (options?.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode) + { + Assert.IsAssignableFrom(roundTripObj.MyOverflow["test"]); + Assert.IsAssignableFrom(roundTripObj.MyOverflow["test1"]); + Assert.IsAssignableFrom(roundTripObj.MyOverflow["test2"]); + Assert.IsAssignableFrom(roundTripObj.MyOverflow["test3"]); + + Assert.IsType(roundTripObj.MyOverflow["test"]); + + Assert.IsAssignableFrom(roundTripObj.MyOverflow["test1"]); + Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue()); + Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue()); + + Assert.IsAssignableFrom(roundTripObj.MyOverflow["test2"]); + Assert.Equal("text", ((JsonValue)roundTripObj.MyOverflow["test2"]).GetValue()); + + Assert.IsType(roundTripObj.MyOverflow["test3"]); + Assert.Equal("ObjectProp", ((JsonObject)roundTripObj.MyOverflow["test3"])["Prop"].GetValue()); + + Assert.IsType(roundTripObj.MyOverflow["test4"]); + Assert.Equal("StructProp", ((JsonObject)roundTripObj.MyOverflow["test4"])["Prop"].GetValue()); + + Assert.IsType(roundTripObj.MyOverflow["test5"]); + Assert.Equal("Value", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key"].GetValue()); + Assert.Equal("Value1", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key1"].GetValue()); + } + else + { + Assert.IsType(roundTripObj.MyOverflow["test"]); + Assert.IsType(roundTripObj.MyOverflow["test1"]); + Assert.IsType(roundTripObj.MyOverflow["test2"]); + Assert.IsType(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(json); + + Assert.Equal(1, obj.MyOverflow.Count); + Assert.Equal(1, obj.MyOverflow["MyDict"]["Property1"].GetValue()); + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Compile-time errors thrown for these scenarios.")] +#endif + + public async Task DeserializeIntoSystemObjectProperty() + { + string json = @"{""MyDict"":{""Property1"":1}}"; + + await Assert.ThrowsAsync(async () => + await JsonSerializerWrapperForString.DeserializeWrapper(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(async () => + await JsonSerializerWrapperForString.DeserializeWrapper(json)); + } + + public class ClassWithReference + { + [JsonExtensionData] + public Dictionary 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(obj.MyOverflow["MyIntMissing"]); + Assert.Equal(1, obj.MyOverflow.Count); + Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32()); + + ClassWithExtensionProperty child = obj.MyReference; + + Assert.IsType(child.MyOverflow["MyIntMissingChild"]); + Assert.IsType(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(json); + Verify(); + + // Round-trip the json and verify. + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + Verify(); + } + + public class ParentClassWithObject + { + public string Text { get; set; } + public ChildClassWithObject Child { get; set; } + + [JsonExtensionData] + public Dictionary ExtensionData { get; set; } = new Dictionary(); + } + + public class ChildClassWithObject + { + public int Number { get; set; } + + [JsonExtensionData] + public Dictionary ExtensionData { get; set; } = new Dictionary(); + } + + [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(parent.ExtensionData["SpecialInformation"]); + Assert.Equal("I am parent class", (string)parent.ExtensionData["SpecialInformation"]); + Assert.Equal(2, parent.Child.Number); + Assert.IsType(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(json); + + Assert.Equal("Hello World", parent.Text); + Assert.IsType(parent.ExtensionData["SpecialInformation"]); + Assert.Equal("I am parent class", ((JsonElement)parent.ExtensionData["SpecialInformation"]).GetString()); + Assert.Equal(2, parent.Child.Number); + Assert.IsType(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 Children { get; set; } = new List(); + + [JsonExtensionData] + // Use SortedDictionary as verification of supporting derived dictionaries. + public SortedDictionary ExtensionData { get; set; } = new SortedDictionary(); + } + + public class ChildClassWithJsonElement + { + public int Number { get; set; } + + [JsonExtensionData] + public Dictionary ExtensionData { get; set; } = new Dictionary(); + } + + [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(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(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(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(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(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(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(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(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(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(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(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(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(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(normalizedString))); + } + + public class ClassWithEscapedProperty + { + public string \u6C49\u5B57 { get; set; } + public NestedClassWithEscapedProperty Class { get; set; } + + [JsonExtensionData] + public Dictionary Overflow { get; set; } + } + + public class NestedClassWithEscapedProperty + { + public string \u6F22\u5B57 { get; set; } + } + + public class ClassWithInvalidExtensionPropertyStringString + { + [JsonExtensionData] + public Dictionary MyOverflow { get; set; } + } + + public class ClassWithInvalidExtensionPropertyObjectString + { + [JsonExtensionData] + public Dictionary MyOverflow { get; set; } + } + + public class ClassWithInvalidExtensionPropertyStringJsonNode + { + [JsonExtensionData] + public Dictionary MyOverflow { get; set; } + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Compile-time errors thrown for these scenarios.")] +#endif + public async Task ExtensionProperty_InvalidDictionary() + { + var obj1 = new ClassWithInvalidExtensionPropertyStringString(); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj1)); + + var obj2 = new ClassWithInvalidExtensionPropertyObjectString(); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj2)); + + var obj3 = new ClassWithInvalidExtensionPropertyStringJsonNode(); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj3)); + } + + public class ClassWithExtensionPropertyAlreadyInstantiated + { + public ClassWithExtensionPropertyAlreadyInstantiated() + { + MyOverflow = new Dictionary(); + } + + [JsonExtensionData] + public Dictionary MyOverflow { get; set; } + } + + public class ClassWithExtensionPropertyAsObject + { + [JsonExtensionData] + public Dictionary MyOverflow { get; set; } + } + + public class ClassWithExtensionPropertyAsJsonElement + { + [JsonExtensionData] + public Dictionary 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 MyOverflow { get; set; } + + public Dictionary ActualDictionary { get; set; } + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Compile-time errors thrown for these scenarios.")] +#endif + public async Task DeserializeIntoImmutableDictionaryProperty() + { + // baseline + await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); + await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); + await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); + await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); + + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{\"hello\":\"world\"}")); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{\"hello\":\"world\"}")); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{\"hello\":\"world\"}")); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{\"hello\":\"world\"}")); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{\"hello\":\"world\"}")); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{\"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.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 { { "hello", "world" } }; + obj.MyOverflow = ImmutableDictionary.CreateRange(dictionaryStringObject); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal(expectedJson, json); + } + + public class ClassWithExtensionPropertyAsImmutable + { + [JsonExtensionData] + public ImmutableDictionary MyOverflow { get; set; } + } + + public class ClassWithExtensionPropertyAsImmutableJsonElement + { + [JsonExtensionData] + public ImmutableDictionary MyOverflow { get; set; } + } + + public class ClassWithExtensionPropertyPrivateConstructor + { + [JsonExtensionData] + public GenericIDictionaryWrapperPrivateConstructor MyOverflow { get; set; } + } + + public class ClassWithExtensionPropertyPrivateConstructorJsonElement + { + [JsonExtensionData] + public GenericIDictionaryWrapperPrivateConstructor MyOverflow { get; set; } + } + + public class ClassWithExtensionPropertyCustomIImmutable + { + [JsonExtensionData] + public GenericIImmutableDictionaryWrapper MyOverflow { get; set; } + } + + public class ClassWithExtensionPropertyCustomIImmutableJsonElement + { + [JsonExtensionData] + public GenericIImmutableDictionaryWrapper 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 extData = (IDictionary)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 MyOverflow { get; set; } + } + + public class ClassWithExtensionPropertyThreeGenericParameters + { + [JsonExtensionData] + public GenericIDictonaryWrapperThreeGenericParameters 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(Json, options); + object overflowProp = obj.MyOverflow["hello"]; + Assert.IsType(overflowProp); + Assert.Equal("world!!!", ((string)overflowProp)); + + string newJson = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Equal("{\"hello\":\"world!!!\"}", newJson); + } + + public class ObjectConverter : JsonConverter + { + 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(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 + { + 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(async () => + await JsonSerializerWrapperForString.DeserializeWrapper(Json, options)); + + Assert.Contains("JsonObject", ex.ToString()); + } + + public class JsonObjectConverter : JsonConverter + { + 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(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(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(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(json, options); + Assert.Equal(43, obj.MyInt1); + Assert.Null(obj.MyOverflow); + } + + //[Fact] + //public async Task QuickDictTest() + //{ + // if (!Diagnostics.Debugger.IsAttached) { Diagnostics.Debugger.Launch(); } + // var dict = await JsonSerializerWrapperForString.DeserializeWrapper>("{}"); + // Console.WriteLine(dict.Count); + //} + + [Fact] + public async Task ExtensionDataDictionarySerialize_DoesNotHonor() + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""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(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 MyOverflow { get; set; } + } + + public class ClassWithEmptyPropertyNameAndExtensionProperty + { + [JsonPropertyName("")] + public int MyInt1 { get; set; } + + [JsonExtensionData] + public IDictionary MyOverflow { get; set; } + } + } +} diff --git a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs index 0d2749c7b13ca6..890d61f97245d1 100644 --- a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs +++ b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs @@ -923,7 +923,7 @@ public class NullArgTester_Mutable 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 @@ public class ClassWithConstructor_SimpleAndComplexParameters : ITestClassWithPar public ImmutableSortedSet MyStringImmutableSortedSetT { get; } public List MyListOfNullString { get; } - public ClassWithConstructor_SimpleAndComplexParameters( + public ObjWCtorMixedParams( byte myByte, char myChar, string myString, @@ -1011,8 +1011,8 @@ public ClassWithConstructor_SimpleAndComplexParameters( MyListOfNullString = myListOfNullString; } - public static ClassWithConstructor_SimpleAndComplexParameters GetInstance() => - JsonSerializer.Deserialize(s_json); + public static ObjWCtorMixedParams GetInstance() => + JsonSerializer.Deserialize(s_json); public static string s_json => $"{{{s_partialJson1},{s_partialJson2}}}"; @@ -1849,23 +1849,23 @@ public class CampaignSummaryViewModel 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 = @" { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs index 01a7e4d0e39f96..f0d1cb897ce2dd 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs @@ -49,7 +49,7 @@ protected ConstructorTests_Metadata(JsonSerializerWrapperForString stringWrapper [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 @@ protected ConstructorTests_Metadata(JsonSerializerWrapperForString stringWrapper [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 @@ public ConstructorTests_Default() [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 @@ public ConstructorTests_Default() [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 index 00000000000000..22b67e71fe800a --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ExtensionDataTests.cs @@ -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))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(CustomOverflowDictionary))] + [JsonSerializable(typeof(CustomOverflowDictionary))] + [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))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(CustomOverflowDictionary))] + [JsonSerializable(typeof(CustomOverflowDictionary))] + [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))] + [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))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(CustomOverflowDictionary))] + [JsonSerializable(typeof(CustomOverflowDictionary))] + [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))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(CustomOverflowDictionary))] + [JsonSerializable(typeof(CustomOverflowDictionary))] + [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))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(DummyObj))] + [JsonSerializable(typeof(DummyStruct))] + internal sealed partial class ExtensionDataTestsContext_Default : JsonSerializerContext + { + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj index f0bdb59b5b67fa..01cb1a20732235 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -38,6 +38,7 @@ + @@ -67,6 +68,7 @@ + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExtensionDataTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExtensionDataTests.cs index 7a64f8ecc66c0e..00ca72aaeee5ea 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExtensionDataTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExtensionDataTests.cs @@ -1,1445 +1,10 @@ // 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(json, options); - Assert.Equal(1, obj.MyInt1); - Assert.Null(obj.MyOverflow); - } - - [Fact] - public static void EmptyPropertyNameInExtensionData() - { - { - string json = @"{"""":42}"; - EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize(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(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(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(json); - Verify(); - } - - // Round-trip the json. - { - string json = JsonSerializer.Serialize(obj); - obj = JsonSerializer.Deserialize(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(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(json); - Verify(); - } - - // Round-trip the json. - { - string json = JsonSerializer.Serialize(obj); - obj = JsonSerializer.Deserialize(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() - }; - actual = JsonSerializer.Serialize(obj); - Assert.Equal("{\"ActualDictionary\":{}}", actual); - - obj = new ClassWithMultipleDictionaries - { - MyOverflow = new Dictionary - { - { "test", "value" } - } - }; - actual = JsonSerializer.Serialize(obj); - Assert.Equal("{\"ActualDictionary\":null,\"test\":\"value\"}", actual); - - obj = new ClassWithMultipleDictionaries - { - ActualDictionary = new Dictionary(), - MyOverflow = new Dictionary - { - { "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(() => JsonSerializer.Deserialize(BadJson)); - Assert.Contains("Path: $.Bad | LineNumber: 0 | BytePositionInLine: 19.", jsonException.ToString()); - Assert.NotNull(jsonException.InnerException); - Assert.IsAssignableFrom(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(json); - Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32()); - } - - [Fact] - public static void ExtensionPropertyAsObject() - { - string json = @"{""MyIntMissing"":2}"; - - ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize(json); - Assert.IsType(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(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(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(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(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(json); - Assert.Null(obj.MyOverflow); - - string outJson = JsonSerializer.Serialize(obj); - Assert.Contains(@"""MyNestedClass"":null", outJson); - } - - { - // Baseline with missing. - ClassWithExtensionProperty obj = JsonSerializer.Deserialize(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(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 MyOverflow { get; set; } - } - - private class ClassWithTwoExtensionProperties - { - [JsonExtensionData] - public Dictionary MyOverflow1 { get; set; } - - [JsonExtensionData] - public Dictionary MyOverflow2 { get; set; } - } - - [Fact] - public static void InvalidExtensionPropertyFail() - { - // Baseline - JsonSerializer.Deserialize(@"{}"); - JsonSerializer.Deserialize(@"{}"); - - Assert.Throws(() => JsonSerializer.Deserialize(@"{}")); - Assert.Throws(() => JsonSerializer.Deserialize(@"{}")); - } - - private class ClassWithIgnoredData - { - [JsonExtensionData] - public Dictionary MyOverflow { get; set; } - - [JsonIgnore] - public int MyInt { get; set; } - } - - [Fact] - public static void IgnoredDataShouldNotBeExtensionData() - { - ClassWithIgnoredData obj = JsonSerializer.Deserialize(@"{""MyInt"":1}"); - - Assert.Equal(0, obj.MyInt); - Assert.Null(obj.MyOverflow); - } - - private class ClassWithExtensionData - { - [JsonExtensionData] - public T Overflow { get; set; } - } - - public class CustomOverflowDictionary : Dictionary - { - } - - public class DictionaryOverflowConverter : JsonConverter> - { - public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - - public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) - { - writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite"); - } - } - - public class JsonElementOverflowConverter : JsonConverter> - { - public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - - public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) - { - writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite"); - } - } - - public class JsonObjectOverflowConverter : JsonConverter - { - 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> - { - public override CustomOverflowDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - - public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary value, JsonSerializerOptions options) - { - writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite"); - } - } - - public class CustomJsonElementDictionaryOverflowConverter : JsonConverter> - { - public override CustomOverflowDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - - public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary value, JsonSerializerOptions options) - { - writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite"); - } - } - - [Theory] - [InlineData(typeof(Dictionary), typeof(DictionaryOverflowConverter))] - [InlineData(typeof(Dictionary), typeof(JsonElementOverflowConverter))] - [InlineData(typeof(CustomOverflowDictionary), typeof(CustomObjectDictionaryOverflowConverter))] - [InlineData(typeof(CustomOverflowDictionary), 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() - where TDictionary : new() - where TConverter : JsonConverter, new() - { - var root = new ClassWithExtensionData() - { - 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 - { - public T Overflow { get; set; } - } - - private class ClassWithExtensionDataWithAttributedConverter : IClassWithOverflow> - { - [JsonExtensionData] - [JsonConverter(typeof(DictionaryOverflowConverter))] - public Dictionary Overflow { get; set; } - } - - private class ClassWithJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow> - { - [JsonExtensionData] - [JsonConverter(typeof(JsonElementOverflowConverter))] - public Dictionary Overflow { get; set; } - } - - private class ClassWithCustomElementExtensionDataWithAttributedConverter : IClassWithOverflow> - { - [JsonExtensionData] - [JsonConverter(typeof(CustomObjectDictionaryOverflowConverter))] - public CustomOverflowDictionary Overflow { get; set; } - } - - private class ClassWithCustomJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow> - { - [JsonExtensionData] - [JsonConverter(typeof(CustomJsonElementDictionaryOverflowConverter))] - public CustomOverflowDictionary Overflow { get; set; } - } - - [Theory] - [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary))] - [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary))] - [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary))] - [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary))] - 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() - where TRoot : IClassWithOverflow, 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), typeof(DictionaryOverflowConverter), typeof(object))] - [InlineData(typeof(Dictionary), typeof(JsonElementOverflowConverter), typeof(JsonElement))] - [InlineData(typeof(CustomOverflowDictionary), typeof(CustomObjectDictionaryOverflowConverter), typeof(object))] - [InlineData(typeof(CustomOverflowDictionary), 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(() => - JsonSerializer.Deserialize>(@"{""TestKey"":""TestValue""}", options)); - - Assert.Contains("JsonObject", ex.ToString()); - } - - private static void ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal() - where TConverter : JsonConverter, new() - where TDictionary : IDictionary - { - var options = new JsonSerializerOptions(); - options.Converters.Add(new TConverter()); - - ClassWithExtensionData obj - = JsonSerializer.Deserialize>(@"{""TestKey"":""TestValue""}", options); - - Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString()); - } - - [Theory] - [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary), typeof(object))] - [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary), typeof(JsonElement))] - [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary), typeof(object))] - [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary), 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 obj - = JsonSerializer.Deserialize>(@"{""TestKey"":""TestValue""}"); - - Assert.Equal("TestValue", obj.Overflow["TestKey"].GetValue()); - } - - private static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal() - where TRoot : IClassWithOverflow, new() - where TDictionary : IDictionary - { - ClassWithExtensionData obj - = JsonSerializer.Deserialize>(@"{""TestKey"":""TestValue""}"); - - Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString()); - } - - [Fact] - public static void ExtensionPropertyObjectValue_Empty() - { - ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Deserialize(@"{}"); - 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(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 MyOverflow { get; set; } - } - - public static IEnumerable 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(); - 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(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(json, options); - - // A null value maps to , so the value is null. - object elem = obj.MyOverflow["MissingProperty"]; - Assert.Null(elem); - } - - { - ClassWithExtensionPropertyAsJsonObject obj = JsonSerializer.Deserialize(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(json); - object elem = obj.MyOverflow["MissingProperty"]; - // Since JsonElement is a struct, it treats null as JsonValueKind.Null. - Assert.IsType(elem); - Assert.Equal(JsonValueKind.Null, ((JsonElement)elem).ValueKind); - } - - [Fact] - public static void Null_JsonObject() - { - const string json = @"{""MissingProperty"":null}"; - - ClassWithExtensionPropertyAsJsonObject obj = JsonSerializer.Deserialize(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(@"{}"); - 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(@"{}", 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() { { "Key", "Value" }, { "Key1", "Value1" }, }); - - string json = JsonSerializer.Serialize(obj); - ClassWithExtensionPropertyAlreadyInstantiated roundTripObj = JsonSerializer.Deserialize(json, options); - - Assert.Equal(6, roundTripObj.MyOverflow.Count); - - if (options?.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode) - { - Assert.IsAssignableFrom(roundTripObj.MyOverflow["test"]); - Assert.IsAssignableFrom(roundTripObj.MyOverflow["test1"]); - Assert.IsAssignableFrom(roundTripObj.MyOverflow["test2"]); - Assert.IsAssignableFrom(roundTripObj.MyOverflow["test3"]); - - Assert.IsType(roundTripObj.MyOverflow["test"]); - - Assert.IsAssignableFrom(roundTripObj.MyOverflow["test1"]); - Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue()); - Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue()); - - Assert.IsAssignableFrom(roundTripObj.MyOverflow["test2"]); - Assert.Equal("text", ((JsonValue)roundTripObj.MyOverflow["test2"]).GetValue()); - - Assert.IsType(roundTripObj.MyOverflow["test3"]); - Assert.Equal("ObjectProp", ((JsonObject)roundTripObj.MyOverflow["test3"])["Prop"].GetValue()); - - Assert.IsType(roundTripObj.MyOverflow["test4"]); - Assert.Equal("StructProp", ((JsonObject)roundTripObj.MyOverflow["test4"])["Prop"].GetValue()); - - Assert.IsType(roundTripObj.MyOverflow["test5"]); - Assert.Equal("Value", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key"].GetValue()); - Assert.Equal("Value1", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key1"].GetValue()); - } - else - { - Assert.IsType(roundTripObj.MyOverflow["test"]); - Assert.IsType(roundTripObj.MyOverflow["test1"]); - Assert.IsType(roundTripObj.MyOverflow["test2"]); - Assert.IsType(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(json); - - Assert.Equal(1, obj.MyOverflow.Count); - Assert.Equal(1, obj.MyOverflow["MyDict"]["Property1"].GetValue()); - } - - [Fact] - public static void DeserializeIntoSystemObjectProperty() - { - string json = @"{""MyDict"":{""Property1"":1}}"; - - Assert.Throws(() => - JsonSerializer.Deserialize(json)); - - // Cannot deserialize into System.Object overflow even if UnknownTypeHandling is set to use JsonNode. - var options = new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode }; - Assert.Throws(() => - JsonSerializer.Deserialize(json)); - } - - private class ClassWithReference - { - [JsonExtensionData] - public Dictionary 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(obj.MyOverflow["MyIntMissing"]); - Assert.Equal(1, obj.MyOverflow.Count); - Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32()); - - ClassWithExtensionProperty child = obj.MyReference; - - Assert.IsType(child.MyOverflow["MyIntMissingChild"]); - Assert.IsType(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(json); - Verify(); - - // Round-trip the json and verify. - json = JsonSerializer.Serialize(obj); - obj = JsonSerializer.Deserialize(json); - Verify(); - } - - private class ParentClassWithObject - { - public string Text { get; set; } - public ChildClassWithObject Child { get; set; } - - [JsonExtensionData] - public Dictionary ExtensionData { get; set; } = new Dictionary(); - } - - private class ChildClassWithObject - { - public int Number { get; set; } - - [JsonExtensionData] - public Dictionary ExtensionData { get; set; } = new Dictionary(); - } - - [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(parent.ExtensionData["SpecialInformation"]); - Assert.Equal("I am parent class", (string)parent.ExtensionData["SpecialInformation"]); - Assert.Equal(2, parent.Child.Number); - Assert.IsType(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(json); - - Assert.Equal("Hello World", parent.Text); - Assert.IsType(parent.ExtensionData["SpecialInformation"]); - Assert.Equal("I am parent class", ((JsonElement)parent.ExtensionData["SpecialInformation"]).GetString()); - Assert.Equal(2, parent.Child.Number); - Assert.IsType(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 Children { get; set; } = new List(); - - [JsonExtensionData] - // Use SortedDictionary as verification of supporting derived dictionaries. - public SortedDictionary ExtensionData { get; set; } = new SortedDictionary(); - } - - private class ChildClassWithJsonElement - { - public int Number { get; set; } - - [JsonExtensionData] - public Dictionary ExtensionData { get; set; } = new Dictionary(); - } - - [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(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(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(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(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(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(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(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(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(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(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(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(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(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(normalizedString))); - } - - public class ClassWithEscapedProperty - { - public string \u6C49\u5B57 { get; set; } - public NestedClassWithEscapedProperty Class { get; set; } - - [JsonExtensionData] - public Dictionary Overflow { get; set; } - } - - public class NestedClassWithEscapedProperty - { - public string \u6F22\u5B57 { get; set; } - } - - private class ClassWithInvalidExtensionPropertyStringString - { - [JsonExtensionData] - public Dictionary MyOverflow { get; set; } - } - - private class ClassWithInvalidExtensionPropertyObjectString - { - [JsonExtensionData] - public Dictionary MyOverflow { get; set; } - } - - private class ClassWithInvalidExtensionPropertyStringJsonNode - { - [JsonExtensionData] - public Dictionary MyOverflow { get; set; } - } - - [Fact] - public static void ExtensionProperty_InvalidDictionary() - { - var obj1 = new ClassWithInvalidExtensionPropertyStringString(); - Assert.Throws(() => JsonSerializer.Serialize(obj1)); - - var obj2 = new ClassWithInvalidExtensionPropertyObjectString(); - Assert.Throws(() => JsonSerializer.Serialize(obj2)); - - var obj3 = new ClassWithInvalidExtensionPropertyStringJsonNode(); - Assert.Throws(() => JsonSerializer.Serialize(obj3)); - } - - private class ClassWithExtensionPropertyAlreadyInstantiated - { - public ClassWithExtensionPropertyAlreadyInstantiated() - { - MyOverflow = new Dictionary(); - } - - [JsonExtensionData] - public Dictionary MyOverflow { get; set; } - } - - private class ClassWithExtensionPropertyAsObject - { - [JsonExtensionData] - public Dictionary MyOverflow { get; set; } - } - - private class ClassWithExtensionPropertyAsJsonElement - { - [JsonExtensionData] - public Dictionary 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 MyOverflow { get; set; } - - public Dictionary ActualDictionary { get; set; } - } - - [Fact] - public static void DeserializeIntoImmutableDictionaryProperty() - { - // baseline - JsonSerializer.Deserialize(@"{}"); - JsonSerializer.Deserialize(@"{}"); - JsonSerializer.Deserialize(@"{}"); - JsonSerializer.Deserialize(@"{}"); - - Assert.Throws(() => JsonSerializer.Deserialize("{\"hello\":\"world\"}")); - Assert.Throws(() => JsonSerializer.Deserialize("{\"hello\":\"world\"}")); - Assert.Throws(() => JsonSerializer.Deserialize("{\"hello\":\"world\"}")); - Assert.Throws(() => JsonSerializer.Deserialize("{\"hello\":\"world\"}")); - Assert.Throws(() => JsonSerializer.Deserialize("{\"hello\":\"world\"}")); - Assert.Throws(() => JsonSerializer.Deserialize("{\"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.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 { { "hello", "world" } }; - obj.MyOverflow = ImmutableDictionary.CreateRange(dictionaryStringObject); - json = JsonSerializer.Serialize(obj); - Assert.Equal(expectedJson, json); - } - - private class ClassWithExtensionPropertyAsImmutable - { - [JsonExtensionData] - public ImmutableDictionary MyOverflow { get; set; } - } - - private class ClassWithExtensionPropertyAsImmutableJsonElement - { - [JsonExtensionData] - public ImmutableDictionary MyOverflow { get; set; } - } - - private class ClassWithExtensionPropertyPrivateConstructor - { - [JsonExtensionData] - public GenericIDictionaryWrapperPrivateConstructor MyOverflow { get; set; } - } - - private class ClassWithExtensionPropertyPrivateConstructorJsonElement - { - [JsonExtensionData] - public GenericIDictionaryWrapperPrivateConstructor MyOverflow { get; set; } - } - - private class ClassWithExtensionPropertyCustomIImmutable - { - [JsonExtensionData] - public GenericIImmutableDictionaryWrapper MyOverflow { get; set; } - } - - private class ClassWithExtensionPropertyCustomIImmutableJsonElement - { - [JsonExtensionData] - public GenericIImmutableDictionaryWrapper 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 extData = (IDictionary)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 MyOverflow { get; set; } - } - - private class ClassWithExtensionPropertyThreeGenericParameters - { - [JsonExtensionData] - public GenericIDictonaryWrapperThreeGenericParameters 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(Json, options); - object overflowProp = obj.MyOverflow["hello"]; - Assert.IsType(overflowProp); - Assert.Equal("world!!!", ((string)overflowProp)); - - string newJson = JsonSerializer.Serialize(obj, options); - Assert.Equal("{\"hello\":\"world!!!\"}", newJson); - } - - private class ObjectConverter : JsonConverter - { - 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(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 - { - 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(() => - JsonSerializer.Deserialize(Json, options)); - - Assert.Contains("JsonObject", ex.ToString()); - } - - private class JsonObjectConverter : JsonConverter - { - 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(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(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(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(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(@"{""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(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 MyOverflow { get; set; } - } - - public class ClassWithEmptyPropertyNameAndExtensionProperty - { - [JsonPropertyName("")] - public int MyInt1 { get; set; } - - [JsonExtensionData] - public IDictionary MyOverflow { get; set; } - } + public ExtensionDataTestsDynamic() : base(JsonSerializerWrapperForString.StringSerializer) { } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 63f6131dfcd844..e360d321aa4497 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -40,6 +40,7 @@ + From e6406f1700a9bd2676ab64d04a3110fcd7081afc Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 10 Sep 2021 07:44:12 -0700 Subject: [PATCH 2/2] Address review feedback --- .../gen/Reflection/TypeExtensions.cs | 1 - .../System.Text.Json/gen/Resources/Strings.resx | 2 +- .../gen/Resources/xlf/Strings.cs.xlf | 4 ++-- .../gen/Resources/xlf/Strings.de.xlf | 4 ++-- .../gen/Resources/xlf/Strings.es.xlf | 4 ++-- .../gen/Resources/xlf/Strings.fr.xlf | 4 ++-- .../gen/Resources/xlf/Strings.it.xlf | 4 ++-- .../gen/Resources/xlf/Strings.ja.xlf | 4 ++-- .../gen/Resources/xlf/Strings.ko.xlf | 4 ++-- .../gen/Resources/xlf/Strings.pl.xlf | 4 ++-- .../gen/Resources/xlf/Strings.pt-BR.xlf | 4 ++-- .../gen/Resources/xlf/Strings.ru.xlf | 4 ++-- .../gen/Resources/xlf/Strings.tr.xlf | 4 ++-- .../gen/Resources/xlf/Strings.zh-Hans.xlf | 4 ++-- .../gen/Resources/xlf/Strings.zh-Hant.xlf | 4 ++-- .../System.Text.Json/gen/TypeGenerationSpec.cs | 4 ++++ .../System.Text.Json/src/Resources/Strings.resx | 2 +- .../tests/Common/ExtensionDataTests.cs | 17 ++++------------- 18 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs index 90e3333a929db1..ba20339565cbf9 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text.Json.SourceGeneration; namespace System.Text.Json.Reflection { diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index 3b877ae96ab239..389b14eae8452f 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -148,7 +148,7 @@ Type has multiple members annotated with JsonExtensionDataAttribute. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. Data extension property type invalid. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf index a038312d189786..710c988fa50f18 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf index 360e9e5be253c7..e4c65b68fad573 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf index 091631a9c2fe07..e97f5789c1b0ae 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf index 9d8a78e10d74f0..d4daa7c3a925d0 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf index fc80dbedab2bf3..b72f65668d00f7 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf index 913d6c8edc9939..17f511d22d9033 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf index 4c79cc3bc7e099..1a458268c19213 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf index 2dc1b07a99c8c8..e757ef48a993e4 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf index 4576c53882eaab..34765df4812c10 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf index b321e422517e66..bdac6af0cb198b 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf index 499dbe33b8e369..e8eedfafa5f515 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf index fd4103a4505f9e..12bee676f0a449 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf index 0d8bc1ae08c64a..0f1c6dd91acdb5 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -13,8 +13,8 @@ - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index b61d7810a227f6..819f9529af2db9 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -58,6 +58,10 @@ internal class TypeGenerationSpec public TypeGenerationSpec? NullableUnderlyingTypeMetadata { get; private set; } + /// + /// Supports deserialization of extension data dictionaries typed as I[ReadOnly]Dictionary. + /// Specifies a concrete type to instanciate, which would be Dictionary. + /// public string? RuntimeTypeRef { get; private set; } public TypeGenerationSpec? ExtensionDataPropertyTypeSpec { get; private set; } diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index e1cb127f8b0cbe..6485157058f537 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -349,7 +349,7 @@ The JSON property name for '{0}.{1}' cannot be null. - The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. The type '{0}' cannot have more than one member that has the attribute '{1}'. diff --git a/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs b/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs index 0c4724d5e293cb..efa6bfb7e121ff 100644 --- a/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs @@ -103,7 +103,6 @@ void Verify() #endif public async Task ExtensionFieldNotUsed() { - Diagnostics.Debugger.Launch(); string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}"; ClassWithExtensionField obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Null(obj.MyOverflow); @@ -323,7 +322,7 @@ public class ClassWithTwoExtensionProperties [Fact] #if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Compile-time errors thrown for these scenarios.")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")] #endif public async Task InvalidExtensionPropertyFail() { @@ -797,7 +796,7 @@ public async Task DeserializeIntoJsonObjectProperty() [Fact] #if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Compile-time errors thrown for these scenarios.")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")] #endif public async Task DeserializeIntoSystemObjectProperty() @@ -1087,7 +1086,7 @@ public class ClassWithInvalidExtensionPropertyStringJsonNode [Fact] #if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Compile-time errors thrown for these scenarios.")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")] #endif public async Task ExtensionProperty_InvalidDictionary() { @@ -1146,7 +1145,7 @@ public class ClassWithMultipleDictionaries [Fact] #if BUILDING_SOURCE_GENERATOR_TESTS - [ActiveIssue("Compile-time errors thrown for these scenarios.")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")] #endif public async Task DeserializeIntoImmutableDictionaryProperty() { @@ -1402,14 +1401,6 @@ public async Task EmptyPropertyNameAndExtensionData_ExtDataFirst() Assert.Null(obj.MyOverflow); } - //[Fact] - //public async Task QuickDictTest() - //{ - // if (!Diagnostics.Debugger.IsAttached) { Diagnostics.Debugger.Launch(); } - // var dict = await JsonSerializerWrapperForString.DeserializeWrapper>("{}"); - // Console.WriteLine(dict.Count); - //} - [Fact] public async Task ExtensionDataDictionarySerialize_DoesNotHonor() {