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";
GenerateTypeInfo(spec.TypeGenerationSpec);
}
}
+
+ TypeGenerationSpec? extPropTypeSpec = typeGenerationSpec.ExtensionDataPropertyTypeSpec;
+ if (extPropTypeSpec != null)
+ {
+ GenerateTypeInfo(extPropTypeSpec);
+ }
}
break;
case ClassType.KnownUnsupportedType:
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
{
{setterNamedArg},
{ignoreConditionNamedArg},
hasJsonInclude: {ToCSharpKeyword(memberMetadata.HasJsonInclude)},
+ isExtensionData: {ToCSharpKeyword(memberMetadata.IsExtensionData)},
numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)},
propertyName: ""{clrPropertyName}"",
{jsonPropertyNameNamedArg});
{
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";
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;
private readonly Type? _uriType;
private readonly Type? _versionType;
private readonly Type? _jsonElementType;
+ private readonly Type? _jsonObjectType;
// Unsupported types
private readonly Type _typeType;
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;
_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));
_typeGenerationSpecCache[type] = typeMetadata;
ClassType classType;
- Type? collectionKeyType = null;
- Type? collectionValueType = null;
+ TypeGenerationSpec? collectionKeyTypeSpec = null;
+ TypeGenerationSpec? collectionValueTypeSpec = null;
TypeGenerationSpec? nullableUnderlyingTypeGenSpec = null;
+ TypeGenerationSpec? dataExtensionPropGenSpec = null;
+ string? runtimeTypeRef = null;
List<PropertyGenerationSpec>? propGenSpecList = null;
ObjectConstructionStrategy constructionStrategy = default;
ParameterGenerationSpec[]? paramGenSpecArray = null;
}
Type actualTypeToConvert;
+ Type? keyType = null;
+ Type valueType;
+ bool needsRuntimeType = false;
if (type.IsArray)
{
? 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)
{
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))
{
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)
{
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)
{
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) ||
// 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);
+ }
}
}
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,
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);
ignoredMember.IsVirtual;
}
- private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, bool isVirtual, JsonSourceGenerationMode generationMode)
+ private PropertyGenerationSpec GetPropertyGenerationSpec(
+ MemberInfo memberInfo,
+ bool isVirtual,
+ JsonSourceGenerationMode generationMode)
{
Type memberCLRType = GetMemberClrType(memberInfo);
IList<CustomAttributeData> attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo);
- ProcessCustomAttributes(
+ ProcessMemberCustomAttributes(
attributeDataList,
memberCLRType,
out bool hasJsonInclude,
out JsonNumberHandling? numberHandling,
out string? converterInstantiationLogic,
out int order,
- out bool hasFactoryConverter);
+ out bool hasFactoryConverter,
+ out bool isExtensionData);
ProcessMember(
memberInfo,
NumberHandling = numberHandling,
Order = order,
HasJsonInclude = hasJsonInclude,
+ IsExtensionData = isExtensionData,
TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode),
DeclaringTypeRef = memberInfo.DeclaringType.GetCompilableName(),
ConverterInstantiationLogic = converterInstantiationLogic,
throw new InvalidOperationException();
}
- private void ProcessCustomAttributes(
+ private void ProcessMemberCustomAttributes(
IList<CustomAttributeData> attributeDataList,
Type memberCLRType,
out bool hasJsonInclude,
out JsonNumberHandling? numberHandling,
out string? converterInstantiationLogic,
out int order,
- out bool hasFactoryConverter)
+ out bool hasFactoryConverter,
+ out bool isExtensionData)
{
hasJsonInclude = false;
jsonPropertyName = null;
numberHandling = default;
converterInstantiationLogic = null;
order = 0;
+ isExtensionData = false;
bool foundDesignTimeCustomConverter = false;
hasFactoryConverter = false;
order = (int)ctorArgs[0].Value;
}
break;
+ case JsonExtensionDataAttributeFullName:
+ {
+ isExtensionData = true;
+ }
+ break;
default:
break;
}
_knownTypes.UnionWith(_numberTypes);
_knownTypes.Add(_booleanType);
- _knownTypes.Add(_byteArrayType);
_knownTypes.Add(_charType);
_knownTypes.Add(_dateTimeType);
- _knownTypes.Add(_dateTimeOffsetType);
- _knownTypes.Add(_guidType);
_knownTypes.Add(_objectType);
_knownTypes.Add(_stringType);
- _knownTypes.Add(_uriType);
- _knownTypes.Add(_versionType);
- _knownTypes.Add(_jsonElementType);
+
+ AddTypeIfNotNull(_knownTypes, _byteArrayType);
+ AddTypeIfNotNull(_knownTypes, _dateTimeOffsetType);
+ AddTypeIfNotNull(_knownTypes, _guidType);
+ AddTypeIfNotNull(_knownTypes, _uriType);
+ AddTypeIfNotNull(_knownTypes, _versionType);
+ AddTypeIfNotNull(_knownTypes, _jsonElementType);
+ AddTypeIfNotNull(_knownTypes, _jsonObjectType);
_knownUnsupportedTypes.Add(_typeType);
_knownUnsupportedTypes.Add(_serializationInfoType);
_knownUnsupportedTypes.Add(_intPtrType);
_knownUnsupportedTypes.Add(_uIntPtrType);
- if (_dateOnlyType != null)
- {
- _knownUnsupportedTypes.Add(_dateOnlyType);
- }
+ AddTypeIfNotNull(_knownUnsupportedTypes, _dateOnlyType);
+ AddTypeIfNotNull(_knownUnsupportedTypes, _timeOnlyType);
- if (_timeOnlyType != null)
+ static void AddTypeIfNotNull(HashSet<Type> types, Type? type)
{
- _knownUnsupportedTypes.Add(_timeOnlyType);
+ if (type != null)
+ {
+ types.Add(type);
+ }
}
}
}
public bool HasJsonInclude { get; init; }
/// <summary>
+ /// Whether the property has the JsonExtensionDataAttribute.
+ /// </summary>
+ public bool IsExtensionData { get; init; }
+
+ /// <summary>
/// Generation specification for the property's type.
/// </summary>
public TypeGenerationSpec TypeGenerationSpec { get; init; }
<data name="MultipleJsonConstructorAttributeTitle" xml:space="preserve">
<value>Type has multiple constructors annotated with JsonConstructorAttribute.</value>
</data>
+ <data name="MultipleJsonExtensionDataAttributeFormat" xml:space="preserve">
+ <value>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</value>
+ </data>
+ <data name="MultipleJsonExtensionDataAttributeTitle" xml:space="preserve">
+ <value>Type has multiple members annotated with JsonExtensionDataAttribute.</value>
+ </data>
+ <data name="DataExtensionPropertyInvalidFormat" xml:space="preserve">
+ <value>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</value>
+ </data>
+ <data name="DataExtensionPropertyInvalidTitle" xml:space="preserve">
+ <value>Data extension property type invalid.</value>
+ </data>
</root>
\ No newline at end of file
<target state="translated">Odvozené typy JsonSerializerContext a všechny obsahující typy musí být částečné.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">Existuje několik typů s názvem {0}. Zdroj se vygeneroval pro první zjištěný typ. Tuto kolizi vyřešíte pomocí JsonSerializableAttribute.TypeInfoPropertyName.</target>
<target state="translated">Typ obsahuje více konstruktorů anotovaných s JsonConstructorAttribute.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">Nevygenerovala se metadata serializace pro typ {0}.</target>
<target state="translated">Abgeleitete JsonSerializerContext-Typen und alle enthaltenden Typen müssen partiell sein.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">Es sind mehrere Typen namens "{0}" vorhanden. Die Quelle wurde für den ersten festgestellten Typ generiert. Verwenden Sie "JsonSerializableAttribute.TypeInfoPropertyName", um diesen Konflikt zu beheben.</target>
<target state="translated">Der Typ weist mehrere Konstruktoren mit dem Kommentar JsonConstructorAttribute auf.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">Die Serialisierungsmetadaten für den Typ "{0}" wurden nicht generiert.</target>
<target state="translated">Los tipos derivados "JsonSerializerContext" y todos los tipos que contienen deben ser parciales.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">Hay varios tipos denominados {0}. El origen se generó para el primero detectado. Use "JsonSerializableAttribute.TypeInfoPropertyName" para resolver esta colisión.</target>
<target state="translated">El tipo tiene varios constructores anotados con JsonConstructorAttribute.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">No generó metadatos de serialización para el tipo '{0}".</target>
<target state="translated">Les types dérivés 'JsonSerializerContext' et tous les types conteneurs doivent être partiels.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">Plusieurs types nommés {0}. La source a été générée pour la première détection détectée. Utilisez « JsonSerializableAttribute.TypeInfoPropertyName » pour résoudre cette collision.</target>
<target state="translated">Le type a plusieurs constructeurs annotés avec JsonConstructorAttribute.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">Les métadonnées de sérialisation pour le type « {0} » n’ont pas été générées.</target>
<target state="translated">I tipi derivati 'JsonSerializerContext' e tutti i tipi contenenti devono essere parziali.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">Sono presenti più tipi denominati {0}. L'origine è stata generata per il primo tipo rilevato. Per risolvere questa collisione, usare 'JsonSerializableAttribute.TypeInfoPropertyName'.</target>
<target state="translated">Il tipo contiene più costruttori che presentano l'annotazione JsonConstructorAttribute.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">Non sono stati generati metadati di serializzazione per il tipo '{0}'.</target>
<target state="translated">派生した 'JsonSerializerContext' 型と含まれているすべての型は部分的である必要があります。</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">{0} と名前が付けられた種類が複数あります。最初に検出されたものに対してソースが生成されました。この問題を解決するには、'JsonSerializableAttribute.TypeInfoPropertyName' を使用します。</target>
<target state="translated">型には、JsonConstructorAttribute で注釈が付けられた複数のコンストラクターがあります。</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">'{0}'型 のシリアル化メタデータを生成ませんでした。</target>
<target state="translated">파생된 'JsonSerializerContext' 형식과 포함하는 모든 형식은 부분이어야 합니다.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">이름이 {0}인 형식이 여러 개 있습니다. 처음 검색한 원본에 대해 원본이 생성되었습니다. 이 충돌을 해결하려면 'JsonSerializableAttribute.TypeInfoPropertyName'을 사용하세요.</target>
<target state="translated">해당 형식에 JsonConstructorAttribute로 주석이 추가된 여러 생성자가 있습니다.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">'{0}' 형식에 대한 직렬화 메타데이터가 생성되지 않았습니다.</target>
<target state="translated">Pochodne typy "JsonSerializerContext" i wszystkich zawierające typy muszą być częściowe.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">Istnieje wiele typów o nazwie {0}. Wygenerowano źródło dla pierwszego wykrytego elementu. Aby rozwiązać tę kolizję, użyj „JsonSerializableAttribute. TypeInfoPropertyName”.</target>
<target state="translated">Typ ma wiele konstruktorów z adnotacją JsonConstructorAttribute.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">Nie wygenerowano metadanych serializacji dla typu „{0}”.</target>
<target state="translated">Os tipos derivados de 'JsonSerializerContext' e todos os tipos contidos devem ser parciais.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">Existem vários tipos chamados {0}. A fonte foi gerada para o primeiro detectado. Use 'JsonSerializableAttribute.TypeInfoPropertyName' para resolver esta colisão.</target>
<target state="translated">O tipo tem vários construtores anotados com JsonConstructorAttribute.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">Não gerou metadados de serialização para o tipo '{0}'.</target>
<target state="translated">Производные типы "JsonSerializerContext" и все содержащие типы должны быть частичными.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">Существует несколько типов с именем {0}. Исходный код сформирован для первого обнаруженного типа. Используйте JsonSerializableAttribute.TypeInfoPropertyName для устранения этого конфликта.</target>
<target state="translated">Тип имеет несколько конструкторов, аннотированных с использованием JsonConstructorAttribute.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">Метаданные сериализации для типа "{0}" не сформированы.</target>
<target state="translated">Türetilmiş 'JsonSerializerContext' türleri ve bunları içeren tüm türler kısmi olmalıdır.</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">{0} adını taşıyan birden çok tür var. Kaynak, algılanan ilk tür için oluşturuldu. Bu çarpışmayı çözmek için 'JsonSerializableAttribute.TypeInfoPropertyName' özelliğini kullanın.</target>
<target state="translated">Türün JsonConstructorAttribute ile açıklanan birden çok oluşturucusu var.</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">'{0}' türü için serileştirme meta verileri oluşturulmadı.</target>
<target state="translated">派生的 “JsonSerializerContext” 类型以及所有包含类型必须是部分。</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">有多个名为 {0} 的类型。已为第一个检测到类型的生成源。请使用 'JsonSerializableAttribute.TypeInfoPropertyName' 以解决此冲突。</target>
<target state="translated">类型具有用 JsonConstructorAttribute 批注的多个构造函数。</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">未生成类型 '{0}' 的序列化元数据。</target>
<target state="translated">衍生的 'JsonSerializerContext' 類型和所有包含類型必須是部份。</target>
<note />
</trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidFormat">
+ <source>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</source>
+ <target state="new">The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="DataExtensionPropertyInvalidTitle">
+ <source>Data extension property type invalid.</source>
+ <target state="new">Data extension property type invalid.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="translated">有多個名為 {0} 的類型。已為偵測到的第一個項目產生來源。請使用 'JsonSerializableAttribute.TypeInfoPropertyName' 解決此衝突。</target>
<target state="translated">類型包含多個以 JsonConstructorAttribute 註解的建構函式。</target>
<note />
</trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeFormat">
+ <source>Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</source>
+ <target state="new">Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="MultipleJsonExtensionDataAttributeTitle">
+ <source>Type has multiple members annotated with JsonExtensionDataAttribute.</source>
+ <target state="new">Type has multiple members annotated with JsonExtensionDataAttribute.</target>
+ <note />
+ </trans-unit>
<trans-unit id="TypeNotSupportedMessageFormat">
<source>Did not generate serialization metadata for type '{0}'.</source>
<target state="translated">未產生類型 '{0}' 的序列化中繼資料。</target>
public TypeGenerationSpec? NullableUnderlyingTypeMetadata { get; private set; }
+ /// <summary>
+ /// Supports deserialization of extension data dictionaries typed as I[ReadOnly]Dictionary<string, object/JsonElement>.
+ /// Specifies a concrete type to instanciate, which would be Dictionary<string, object/JsonElement>.
+ /// </summary>
+ public string? RuntimeTypeRef { get; private set; }
+
+ public TypeGenerationSpec? ExtensionDataPropertyTypeSpec { get; private set; }
+
public string? ConverterInstantiationLogic { get; private set; }
// Only generate certain helper methods if necessary.
TypeGenerationSpec? collectionValueTypeMetadata,
ObjectConstructionStrategy constructionStrategy,
TypeGenerationSpec? nullableUnderlyingTypeMetadata,
+ string? runtimeTypeRef,
+ TypeGenerationSpec? extensionDataPropertyTypeSpec,
string? converterInstantiationLogic,
bool implementsIJsonOnSerialized,
bool implementsIJsonOnSerializing,
CollectionValueTypeMetadata = collectionValueTypeMetadata;
ConstructionStrategy = constructionStrategy;
NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata;
+ RuntimeTypeRef = runtimeTypeRef;
+ ExtensionDataPropertyTypeSpec = extensionDataPropertyTypeSpec;
ConverterInstantiationLogic = converterInstantiationLogic;
ImplementsIJsonOnSerialized = implementsIJsonOnSerialized;
ImplementsIJsonOnSerializing = implementsIJsonOnSerializing;
{
if (ClassType == ClassType.Object)
{
+ if (ExtensionDataPropertyTypeSpec != null)
+ {
+ return false;
+ }
+
foreach (PropertyGenerationSpec property in PropertyGenSpecList)
{
if (property.TypeGenerationSpec.Type.IsObjectType() ||
public static System.Text.Json.Serialization.JsonConverter<int> Int32Converter { get { throw null; } }
public static System.Text.Json.Serialization.JsonConverter<long> Int64Converter { get { throw null; } }
public static System.Text.Json.Serialization.JsonConverter<System.Text.Json.JsonElement> JsonElementConverter { get { throw null; } }
+ public static System.Text.Json.Serialization.JsonConverter<System.Text.Json.Nodes.JsonObject> JsonObjectConverter { get { throw null; } }
public static System.Text.Json.Serialization.JsonConverter<object> ObjectConverter { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public static System.Text.Json.Serialization.JsonConverter<sbyte> SByteConverter { get { throw null; } }
public static JsonTypeInfo<TCollection> CreateISetInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.ISet<TElement> { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.List<TElement> { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateObjectInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<T> objectInfo) where T : notnull { throw null; }
- public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo<T>(System.Text.Json.JsonSerializerOptions options, bool isProperty, bool isPublic, bool isVirtual, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter<T>? converter, System.Func<object, T?>? getter, System.Action<object, T?>? setter, System.Text.Json.Serialization.JsonIgnoreCondition? ignoreCondition, bool hasJsonInclude, System.Text.Json.Serialization.JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) { throw null; }
+ public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo<T>(System.Text.Json.JsonSerializerOptions options, bool isProperty, bool isPublic, bool isVirtual, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter<T>? converter, System.Func<object, T?>? getter, System.Action<object, T?>? setter, System.Text.Json.Serialization.JsonIgnoreCondition? ignoreCondition, bool hasJsonInclude, bool isExtensionData, System.Text.Json.Serialization.JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) { throw null; }
public static JsonTypeInfo<TCollection> CreateQueueInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.Queue<TElement> { throw null; }
public static JsonTypeInfo<TCollection> CreateStackInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.Stack<TElement> { throw null; }
public static JsonTypeInfo<TCollection> CreateStackOrQueueInfo<TCollection>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc, System.Action<TCollection, object?> addFunc) where TCollection : System.Collections.IEnumerable { throw null; }
<value>The JSON property name for '{0}.{1}' cannot be null.</value>
</data>
<data name="SerializationDataExtensionPropertyInvalid" xml:space="preserve">
- <value>The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'.</value>
+ <value>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'.</value>
</data>
<data name="SerializationDuplicateTypeAttribute" xml:space="preserve">
<value>The type '{0}' cannot have more than one member that has the attribute '{1}'.</value>
private JsonConverter<T>? _converter;
// A backing converter for when fast-path logic cannot be used.
- private JsonConverter<T> Converter
+ internal JsonConverter<T> Converter
{
get
{
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
return TryWrite(writer, value, options, ref state);
}
- if (this is not JsonDictionaryConverter<T> dictionaryConverter)
+ JsonDictionaryConverter<T>? dictionaryConverter = this as JsonDictionaryConverter<T>
+ ?? (this as JsonMetadataServicesConverter<T>)?.Converter as JsonDictionaryConverter<T>;
+
+ if (dictionaryConverter == null)
{
// If not JsonDictionaryConverter<T> then we are JsonObject.
// Avoid a type reference to JsonObject and its converter to support trimming.
// 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
private static JsonConverter<JsonElement>? s_jsonElementConverter;
/// <summary>
+ /// Returns a <see cref="JsonConverter{T}"/> instance that converts <see cref="JsonObject"/> values.
+ /// </summary>
+ public static JsonConverter<JsonObject> JsonObjectConverter => s_jsonObjectConverter ??= new JsonObjectConverter();
+ private static JsonConverter<JsonObject>? s_jsonObjectConverter;
+
+ /// <summary>
/// Returns a <see cref="JsonConverter{T}"/> instance that converts <see cref="object"/> values.
/// </summary>
public static JsonConverter<object?> ObjectConverter => s_objectConverter ??= new ObjectConverter();
// 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
/// <param name="ignoreCondition">Specifies a condition for the property to be ignored.</param>
/// <param name="numberHandling">If the property or field is a number, specifies how it should processed when serializing and deserializing.</param>
/// <param name="hasJsonInclude">Whether the property was annotated with <see cref="JsonIncludeAttribute"/>.</param>
+ /// <param name="isExtensionData">Whether the property was annotated with <see cref="JsonExtensionDataAttribute"/>.</param>
/// <param name="propertyName">The CLR name of the property or field.</param>
/// <param name="jsonPropertyName">The name to be used when processing the property or field, specified by <see cref="JsonPropertyNameAttribute"/>.</param>
/// <returns>A <see cref="JsonPropertyInfo"/> instance intialized with the provided metadata.</returns>
Action<object, T?>? setter,
JsonIgnoreCondition? ignoreCondition,
bool hasJsonInclude,
+ bool isExtensionData,
JsonNumberHandling? numberHandling,
string propertyName,
string? jsonPropertyName)
setter,
ignoreCondition,
hasJsonInclude,
+ isExtensionData,
numberHandling,
propertyName,
jsonPropertyName);
internal bool SrcGen_HasJsonInclude { get; set; }
/// <summary>
+ /// Relevant to source generated metadata: did the property have the <see cref="JsonExtensionDataAttribute"/>?
+ /// </summary>
+ internal bool SrcGen_IsExtensionData { get; set; }
+
+ /// <summary>
/// Relevant to source generated metadata: is the property public?
/// </summary>
internal bool SrcGen_IsPublic { get; set; }
Action<object, T?>? setter,
JsonIgnoreCondition? ignoreCondition,
bool hasJsonInclude,
+ bool isExtensionData,
JsonNumberHandling? numberHandling,
string propertyName,
string? jsonPropertyName)
SrcGen_IsPublic = isPublic;
SrcGen_HasJsonInclude = hasJsonInclude;
+ SrcGen_IsExtensionData = isExtensionData;
DeclaredPropertyType = typeof(T);
ConverterBase = converter;
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);
}
else if (DataExtensionProperty != null &&
StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, DataExtensionProperty.NameAsString))
{
- ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty.MemberInfo!, Type);
+ ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty);
}
}
private void ValidateAndAssignDataExtensionProperty(JsonPropertyInfo jsonPropertyInfo)
{
- Type memberType = jsonPropertyInfo.DeclaredPropertyType;
- JsonConverter? converter = null;
- if (typeof(IDictionary<string, object>).IsAssignableFrom(memberType) ||
- typeof(IDictionary<string, JsonElement>).IsAssignableFrom(memberType) ||
- // Avoid a reference to typeof(JsonNode) to support trimming.
- (memberType.FullName == JsonObjectTypeName && ReferenceEquals(memberType.Assembly, GetType().Assembly)))
- {
- converter = Options.GetConverterInternal(memberType);
- }
-
- if (converter == null)
+ if (!IsValidDataExtensionProperty(jsonPropertyInfo))
{
ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(Type, jsonPropertyInfo);
}
DataExtensionProperty = jsonPropertyInfo;
}
+ private bool IsValidDataExtensionProperty(JsonPropertyInfo jsonPropertyInfo)
+ {
+ Type memberType = jsonPropertyInfo.DeclaredPropertyType;
+
+ bool typeIsValid = typeof(IDictionary<string, object>).IsAssignableFrom(memberType) ||
+ typeof(IDictionary<string, JsonElement>).IsAssignableFrom(memberType) ||
+ // Avoid a reference to typeof(JsonNode) to support trimming.
+ (memberType.FullName == JsonObjectTypeName && ReferenceEquals(memberType.Assembly, GetType().Assembly));
+
+ return typeIsValid && Options.GetConverterInternal(memberType) != null;
+ }
+
private static JsonParameterInfo CreateConstructorParameter(
JsonParameterInfoValues parameterInfo,
JsonPropertyInfo jsonPropertyInfo,
[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]
{
[Fact]
[OuterLoop]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task MultipleThreadsLooping()
{
const int Iterations = 100;
}
[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)
{
async Task SerializeObject(Type type, JsonSerializerOptions options)
{
- var obj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+ var obj = ObjWCtorMixedParams.GetInstance();
await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
};
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));
}
var options = new JsonSerializerOptions();
string json = "{}";
- await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+ await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(json, options);
- ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+ ObjWCtorMixedParams testObj = ObjWCtorMixedParams.GetInstance();
testObj.Verify();
json = await JsonSerializerWrapperForString.SerializeWrapper(testObj, options);
- testObj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+ testObj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(json, options);
testObj.Verify();
}
// Use local options to avoid obtaining already cached metadata from the default options.
var options = new JsonSerializerOptions();
- ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+ ObjWCtorMixedParams testObj = ObjWCtorMixedParams.GetInstance();
testObj.Verify();
string json = await JsonSerializerWrapperForString.SerializeWrapper(testObj, options);
- testObj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+ testObj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(json, options);
testObj.Verify();
json = "{}";
- await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+ await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(json, options);
}
// Use a common options instance to encourage additional metadata collisions across types. Also since
[Fact]
[SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", RuntimeConfiguration.Checked)]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task MultipleTypes()
{
async Task Serialize<T>(object[] args)
}
[Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task ExtensionDataProperty_CannotBindTo_CtorParam()
{
InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(() => JsonSerializerWrapperForString.DeserializeWrapper<Class_ExtData_CtorParam>("{}"));
- string exStr = ex.ToString(); // System.InvalidOperationException: 'The extension data property 'System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement] ExtensionData' on type 'System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam' cannot bind with a parameter in constructor 'Void .ctor(System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement])'.'
- Assert.Contains("System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement] ExtensionData", exStr);
+ string exStr = ex.ToString(); // System.InvalidOperationException: 'The extension data property 'ExtensionData' on type 'System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam' cannot bind with a parameter in the deserialization constructor.'
+ Assert.Contains("ExtensionData", exStr);
Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam", exStr);
}
// Baseline (no exception)
{
- var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(@"{""mydecimal"":1}", options);
+ var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(@"{""mydecimal"":1}", options);
Assert.Equal(1, obj.MyDecimal);
}
{
- var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(@"{""MYDECIMAL"":1}", options);
+ var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(@"{""MYDECIMAL"":1}", options);
Assert.Equal(1, obj.MyDecimal);
}
JsonException e;
- e = await Assert.ThrowsAsync<JsonException>(() => JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(@"{""mydecimal"":bad}", options));
+ e = await Assert.ThrowsAsync<JsonException>(() => JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(@"{""mydecimal"":bad}", options));
Assert.Equal("$.mydecimal", e.Path);
- e = await Assert.ThrowsAsync<JsonException>(() => JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(@"{""MYDECIMAL"":bad}", options));
+ e = await Assert.ThrowsAsync<JsonException>(() => JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(@"{""MYDECIMAL"":bad}", options));
Assert.Equal("$.MYDECIMAL", e.Path);
}
}
[Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task OtherPropertiesAreSet()
{
var personClass = await JsonSerializerWrapperForString.DeserializeWrapper<Person_Class>(Person_Class.s_json);
}
[Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task ExtraProperties_GoInExtensionData_IfPresent()
{
Point_2D_With_ExtData point = await JsonSerializerWrapperForString.DeserializeWrapper<Point_2D_With_ExtData>(@"{""X"":1,""y"":2,""b"":3}");
[Fact]
public async Task NumerousSimpleAndComplexParameters()
{
- var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_json);
+ var obj = await JsonSerializerWrapperForString.DeserializeWrapper<ObjWCtorMixedParams>(ObjWCtorMixedParams.s_json);
obj.Verify();
}
#endif
public async Task TupleDeserializationWorks_ClassWithParameterizedCtor()
{
- string classJson = ClassWithConstructor_SimpleAndComplexParameters.s_json;
+ string classJson = ObjWCtorMixedParams.s_json;
StringBuilder sb = new StringBuilder();
sb.Append("{");
string complexTupleJson = sb.ToString();
var complexTuple = await JsonSerializerWrapperForString.DeserializeWrapper<Tuple<
- ClassWithConstructor_SimpleAndComplexParameters,
- ClassWithConstructor_SimpleAndComplexParameters,
- ClassWithConstructor_SimpleAndComplexParameters,
- ClassWithConstructor_SimpleAndComplexParameters,
- ClassWithConstructor_SimpleAndComplexParameters,
- ClassWithConstructor_SimpleAndComplexParameters,
- ClassWithConstructor_SimpleAndComplexParameters>>(complexTupleJson);
+ ObjWCtorMixedParams,
+ ObjWCtorMixedParams,
+ ObjWCtorMixedParams,
+ ObjWCtorMixedParams,
+ ObjWCtorMixedParams,
+ ObjWCtorMixedParams,
+ ObjWCtorMixedParams>>(complexTupleJson);
complexTuple.Item1.Verify();
complexTuple.Item2.Verify();
}
[Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task LastParameterWins_DoesNotGoToExtensionData()
{
string json = @"{
}
[Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task HonorExtensionDataGeneric()
{
var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper<SimpleClassWithParameterizedCtor_GenericDictionary_JsonElementExt>(@"{""key"": ""value""}");
{
[Fact]
[SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", RuntimeConfiguration.Checked)]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task ReadSimpleObjectAsync()
{
async Task RunTestAsync<T>(byte[] testData)
// Simple models can be deserialized.
tasks[0] = Task.Run(async () => await RunTestAsync<Parameterized_IndexViewModel_Immutable>(Parameterized_IndexViewModel_Immutable.s_data));
// Complex models can be deserialized.
- tasks[1] = Task.Run(async () => await RunTestAsync<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_data));
+ tasks[1] = Task.Run(async () => await RunTestAsync<ObjWCtorMixedParams>(ObjWCtorMixedParams.s_data));
tasks[2] = Task.Run(async () => await RunTestAsync<Parameterized_Class_With_ComplexTuple>(Parameterized_Class_With_ComplexTuple.s_data));
// JSON that doesn't bind to ctor args are matched with properties or ignored (as appropriate).
tasks[3] = Task.Run(async () => await RunTestAsync<Person_Class>(Person_Class.s_data));
}
[Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task ReadSimpleObjectWithTrailingTriviaAsync()
{
async Task RunTestAsync<T>(string testData)
// Simple models can be deserialized.
tasks[0] = Task.Run(async () => await RunTestAsync<Parameterized_IndexViewModel_Immutable>(Parameterized_IndexViewModel_Immutable.s_json));
// Complex models can be deserialized.
- tasks[1] = Task.Run(async () => await RunTestAsync<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_json));
+ tasks[1] = Task.Run(async () => await RunTestAsync<ObjWCtorMixedParams>(ObjWCtorMixedParams.s_json));
tasks[2] = Task.Run(async () => await RunTestAsync<Parameterized_Class_With_ComplexTuple>(Parameterized_Class_With_ComplexTuple.s_json));
// JSON that doesn't bind to ctor args are matched with properties or ignored (as appropriate).
tasks[3] = Task.Run(async () => await RunTestAsync<Person_Class>(Person_Class.s_json));
}
[Fact]
-#if BUILDING_SOURCE_GENERATOR_TESTS
- [ActiveIssue("Needs JsonExtensionData support.")]
-#endif
public async Task ExerciseStreamCodePaths()
{
static string GetPropertyName(int index) =>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Reflection;
+using System.Text.Encodings.Web;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+ public abstract class ExtensionDataTests : SerializerTests
+ {
+ public ExtensionDataTests(JsonSerializerWrapperForString serializerWrapper) : base(serializerWrapper, null) { }
+
+ [Fact]
+ public async Task EmptyPropertyName_WinsOver_ExtensionDataEmptyPropertyName()
+ {
+ string json = @"{"""":1}";
+
+ ClassWithEmptyPropertyNameAndExtensionProperty obj;
+
+ // Create a new options instances to re-set any caches.
+ JsonSerializerOptions options = new JsonSerializerOptions();
+
+ // Verify the real property wins over the extension data property.
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+ Assert.Equal(1, obj.MyInt1);
+ Assert.Null(obj.MyOverflow);
+ }
+
+ [Fact]
+ public async Task EmptyPropertyNameInExtensionData()
+ {
+ {
+ string json = @"{"""":42}";
+ EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<EmptyClassWithExtensionProperty>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(42, obj.MyOverflow[""].GetInt32());
+ }
+
+ {
+ // Verify that last-in wins.
+ string json = @"{"""":42, """":43}";
+ EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<EmptyClassWithExtensionProperty>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(43, obj.MyOverflow[""].GetInt32());
+ }
+ }
+
+ [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ [ActiveIssue("Needs SimpleTestClass support.")]
+#endif
+ public async Task ExtensionPropertyNotUsed()
+ {
+ string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
+ ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+ Assert.Null(obj.MyOverflow);
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyRoundTrip()
+ {
+ ClassWithExtensionProperty obj;
+
+ {
+ string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+ Verify();
+ }
+
+ // Round-trip the json.
+ {
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+ Verify();
+
+ // The json should not contain the dictionary name.
+ Assert.DoesNotContain(nameof(ClassWithExtensionProperty.MyOverflow), json);
+ }
+
+ void Verify()
+ {
+ Assert.NotNull(obj.MyOverflow);
+ Assert.Equal(1, obj.MyInt);
+ Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+
+ JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
+
+ // Verify a couple properties
+ Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
+ Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
+ }
+ }
+
+ [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ [ActiveIssue("Needs SimpleTestClass support.")]
+#endif
+ public async Task ExtensionFieldNotUsed()
+ {
+ string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
+ ClassWithExtensionField obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionField>(json);
+ Assert.Null(obj.MyOverflow);
+ }
+
+ [Fact]
+ public async Task ExtensionFieldRoundTrip()
+ {
+ ClassWithExtensionField obj;
+
+ {
+ string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionField>(json);
+ Verify();
+ }
+
+ // Round-trip the json.
+ {
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionField>(json);
+ Verify();
+
+ // The json should not contain the dictionary name.
+ Assert.DoesNotContain(nameof(ClassWithExtensionField.MyOverflow), json);
+ }
+
+ void Verify()
+ {
+ Assert.NotNull(obj.MyOverflow);
+ Assert.Equal(1, obj.MyInt);
+ Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+
+ JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
+
+ // Verify a couple properties
+ Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
+ Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
+ }
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyIgnoredWhenWritingDefault()
+ {
+ string expected = @"{}";
+ string actual = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithExtensionPropertyAsObject());
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public async Task MultipleExtensionPropertyIgnoredWhenWritingDefault()
+ {
+ var obj = new ClassWithMultipleDictionaries();
+ string actual = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal("{\"ActualDictionary\":null}", actual);
+
+ obj = new ClassWithMultipleDictionaries
+ {
+ ActualDictionary = new Dictionary<string, object>()
+ };
+ actual = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal("{\"ActualDictionary\":{}}", actual);
+
+ obj = new ClassWithMultipleDictionaries
+ {
+ MyOverflow = new Dictionary<string, object>
+ {
+ { "test", "value" }
+ }
+ };
+ actual = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal("{\"ActualDictionary\":null,\"test\":\"value\"}", actual);
+
+ obj = new ClassWithMultipleDictionaries
+ {
+ ActualDictionary = new Dictionary<string, object>(),
+ MyOverflow = new Dictionary<string, object>
+ {
+ { "test", "value" }
+ }
+ };
+ actual = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal("{\"ActualDictionary\":{},\"test\":\"value\"}", actual);
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyInvalidJsonFail()
+ {
+ const string BadJson = @"{""Good"":""OK"",""Bad"":!}";
+
+ JsonException jsonException = await Assert.ThrowsAsync<JsonException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(BadJson));
+ Assert.Contains("Path: $.Bad | LineNumber: 0 | BytePositionInLine: 19.", jsonException.ToString());
+ Assert.NotNull(jsonException.InnerException);
+ Assert.IsAssignableFrom<JsonException>(jsonException.InnerException);
+ Assert.Contains("!", jsonException.InnerException.ToString());
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyAlreadyInstantiated()
+ {
+ Assert.NotNull(new ClassWithExtensionPropertyAlreadyInstantiated().MyOverflow);
+
+ string json = @"{""MyIntMissing"":2}";
+
+ ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+ Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyAsObject()
+ {
+ string json = @"{""MyIntMissing"":2}";
+
+ ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+ Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
+ Assert.Equal(2, ((JsonElement)obj.MyOverflow["MyIntMissing"]).GetInt32());
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyCamelCasing()
+ {
+ // Currently we apply no naming policy. If we do (such as a ExtensionPropertyNamingPolicy), we'd also have to add functionality to the JsonDocument.
+
+ ClassWithExtensionProperty obj;
+ const string jsonWithProperty = @"{""MyIntMissing"":1}";
+ const string jsonWithPropertyCamelCased = @"{""myIntMissing"":1}";
+
+ {
+ // Baseline Pascal-cased json + no casing option.
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonWithProperty);
+ Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Contains(@"""MyIntMissing"":1", json);
+ }
+
+ {
+ // Pascal-cased json + camel casing option.
+ JsonSerializerOptions options = new JsonSerializerOptions();
+ options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
+ options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonWithProperty, options);
+ Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+ Assert.Contains(@"""MyIntMissing"":1", json);
+ }
+
+ {
+ // Baseline camel-cased json + no casing option.
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonWithPropertyCamelCased);
+ Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Contains(@"""myIntMissing"":1", json);
+ }
+
+ {
+ // Baseline camel-cased json + camel casing option.
+ JsonSerializerOptions options = new JsonSerializerOptions();
+ options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
+ options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonWithPropertyCamelCased, options);
+ Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+ Assert.Contains(@"""myIntMissing"":1", json);
+ }
+ }
+
+ [Fact]
+ public async Task NullValuesIgnored()
+ {
+ const string json = @"{""MyNestedClass"":null}";
+ const string jsonMissing = @"{""MyNestedClassMissing"":null}";
+
+ {
+ // Baseline with no missing.
+ ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(json);
+ Assert.Null(obj.MyOverflow);
+
+ string outJson = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Contains(@"""MyNestedClass"":null", outJson);
+ }
+
+ {
+ // Baseline with missing.
+ ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonMissing);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyNestedClassMissing"].ValueKind);
+ }
+
+ {
+ JsonSerializerOptions options = new JsonSerializerOptions();
+ options.IgnoreNullValues = true;
+
+ ClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(jsonMissing, options);
+
+ // Currently we do not ignore nulls in the extension data. The JsonDocument would also need to support this mode
+ // for any lower-level nulls.
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyNestedClassMissing"].ValueKind);
+ }
+ }
+
+ public class ClassWithInvalidExtensionProperty
+ {
+ [JsonExtensionData]
+ public Dictionary<string, int> MyOverflow { get; set; }
+ }
+
+ public class ClassWithTwoExtensionProperties
+ {
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow1 { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow2 { get; set; }
+ }
+
+ [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
+#endif
+ public async Task InvalidExtensionPropertyFail()
+ {
+ // Baseline
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionProperty>(@"{}");
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(@"{}");
+
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithInvalidExtensionProperty>(@"{}"));
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithTwoExtensionProperties>(@"{}"));
+ }
+
+ public class ClassWithIgnoredData
+ {
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow { get; set; }
+
+ [JsonIgnore]
+ public int MyInt { get; set; }
+ }
+
+ [Fact]
+ public async Task IgnoredDataShouldNotBeExtensionData()
+ {
+ ClassWithIgnoredData obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithIgnoredData>(@"{""MyInt"":1}");
+
+ Assert.Equal(0, obj.MyInt);
+ Assert.Null(obj.MyOverflow);
+ }
+
+ public class ClassWithExtensionData<T>
+ {
+ [JsonExtensionData]
+ public T Overflow { get; set; }
+ }
+
+ public class CustomOverflowDictionary<T> : Dictionary<string, T>
+ {
+ }
+
+ public class DictionaryOverflowConverter : JsonConverter<Dictionary<string, object>>
+ {
+ public override Dictionary<string, object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, Dictionary<string, object> value, JsonSerializerOptions options)
+ {
+ writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+ }
+ }
+
+ public class JsonElementOverflowConverter : JsonConverter<Dictionary<string, JsonElement>>
+ {
+ public override Dictionary<string, JsonElement> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, Dictionary<string, JsonElement> value, JsonSerializerOptions options)
+ {
+ writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+ }
+ }
+
+ public class JsonObjectOverflowConverter : JsonConverter<JsonObject>
+ {
+ public override JsonObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, JsonObject value, JsonSerializerOptions options)
+ {
+ writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+ }
+ }
+
+ public class CustomObjectDictionaryOverflowConverter : JsonConverter<CustomOverflowDictionary<object>>
+ {
+ public override CustomOverflowDictionary<object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary<object> value, JsonSerializerOptions options)
+ {
+ writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+ }
+ }
+
+ public class CustomJsonElementDictionaryOverflowConverter : JsonConverter<CustomOverflowDictionary<JsonElement>>
+ {
+ public override CustomOverflowDictionary<JsonElement> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary<JsonElement> value, JsonSerializerOptions options)
+ {
+ writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
+ }
+ }
+
+ [Theory]
+ [InlineData(typeof(Dictionary<string, object>), typeof(DictionaryOverflowConverter))]
+ [InlineData(typeof(Dictionary<string, JsonElement>), typeof(JsonElementOverflowConverter))]
+ [InlineData(typeof(CustomOverflowDictionary<object>), typeof(CustomObjectDictionaryOverflowConverter))]
+ [InlineData(typeof(CustomOverflowDictionary<JsonElement>), typeof(CustomJsonElementDictionaryOverflowConverter))]
+ public void ExtensionProperty_SupportsWritingToCustomSerializerWithOptions(Type overflowType, Type converterType)
+ {
+ typeof(ExtensionDataTests)
+ .GetMethod(nameof(ExtensionProperty_SupportsWritingToCustomSerializerWithOptionsInternal), BindingFlags.Static | BindingFlags.NonPublic)
+ .MakeGenericMethod(overflowType, converterType)
+ .Invoke(null, null);
+ }
+
+ private static void ExtensionProperty_SupportsWritingToCustomSerializerWithOptionsInternal<TDictionary, TConverter>()
+ where TDictionary : new()
+ where TConverter : JsonConverter, new()
+ {
+ var root = new ClassWithExtensionData<TDictionary>()
+ {
+ Overflow = new TDictionary()
+ };
+
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new TConverter());
+
+ string json = JsonSerializer.Serialize(root, options);
+ Assert.Equal(@"{""MyCustomOverflowWrite"":""OverflowValueWrite""}", json);
+ }
+
+ private interface IClassWithOverflow<T>
+ {
+ public T Overflow { get; set; }
+ }
+
+ public class ClassWithExtensionDataWithAttributedConverter : IClassWithOverflow<Dictionary<string, object>>
+ {
+ [JsonExtensionData]
+ [JsonConverter(typeof(DictionaryOverflowConverter))]
+ public Dictionary<string, object> Overflow { get; set; }
+ }
+
+ public class ClassWithJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow<Dictionary<string, JsonElement>>
+ {
+ [JsonExtensionData]
+ [JsonConverter(typeof(JsonElementOverflowConverter))]
+ public Dictionary<string, JsonElement> Overflow { get; set; }
+ }
+
+ public class ClassWithCustomElementExtensionDataWithAttributedConverter : IClassWithOverflow<CustomOverflowDictionary<object>>
+ {
+ [JsonExtensionData]
+ [JsonConverter(typeof(CustomObjectDictionaryOverflowConverter))]
+ public CustomOverflowDictionary<object> Overflow { get; set; }
+ }
+
+ public class ClassWithCustomJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow<CustomOverflowDictionary<JsonElement>>
+ {
+ [JsonExtensionData]
+ [JsonConverter(typeof(CustomJsonElementDictionaryOverflowConverter))]
+ public CustomOverflowDictionary<JsonElement> Overflow { get; set; }
+ }
+
+ [Theory]
+ [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary<string, object>))]
+ [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary<string, JsonElement>))]
+ [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<object>))]
+ [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<JsonElement>))]
+ public void ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverter(Type attributedType, Type dictionaryType)
+ {
+ typeof(ExtensionDataTests)
+ .GetMethod(nameof(ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverterInternal), BindingFlags.Static | BindingFlags.NonPublic)
+ .MakeGenericMethod(attributedType, dictionaryType)
+ .Invoke(null, null);
+ }
+
+ private static void ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverterInternal<TRoot, TDictionary>()
+ where TRoot : IClassWithOverflow<TDictionary>, new()
+ where TDictionary : new()
+ {
+ var root = new TRoot()
+ {
+ Overflow = new TDictionary()
+ };
+
+ string json = JsonSerializer.Serialize(root);
+ Assert.Equal(@"{""MyCustomOverflowWrite"":""OverflowValueWrite""}", json);
+ }
+
+ [Theory]
+ [InlineData(typeof(Dictionary<string, object>), typeof(DictionaryOverflowConverter), typeof(object))]
+ [InlineData(typeof(Dictionary<string, JsonElement>), typeof(JsonElementOverflowConverter), typeof(JsonElement))]
+ [InlineData(typeof(CustomOverflowDictionary<object>), typeof(CustomObjectDictionaryOverflowConverter), typeof(object))]
+ [InlineData(typeof(CustomOverflowDictionary<JsonElement>), typeof(CustomJsonElementDictionaryOverflowConverter), typeof(JsonElement))]
+ public void ExtensionProperty_IgnoresCustomSerializerWithOptions(Type overflowType, Type converterType, Type elementType)
+ {
+ typeof(ExtensionDataTests)
+ .GetMethod(nameof(ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal), BindingFlags.Static | BindingFlags.NonPublic)
+ .MakeGenericMethod(overflowType, elementType, converterType)
+ .Invoke(null, null);
+ }
+
+ [Fact]
+ public async Task ExtensionProperty_IgnoresCustomSerializerWithOptions_JsonObject()
+ {
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new JsonObjectOverflowConverter());
+
+ // A custom converter for JsonObject is not allowed on an extension property.
+ InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionData<JsonObject>>(@"{""TestKey"":""TestValue""}", options));
+
+ Assert.Contains("JsonObject", ex.ToString());
+ }
+
+ private static void ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal<TDictionary, TOverflowItem, TConverter>()
+ where TConverter : JsonConverter, new()
+ where TDictionary : IDictionary<string, TOverflowItem>
+ {
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new TConverter());
+
+ ClassWithExtensionData<TDictionary> obj
+ = JsonSerializer.Deserialize<ClassWithExtensionData<TDictionary>>(@"{""TestKey"":""TestValue""}", options);
+
+ Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString());
+ }
+
+ [Theory]
+ [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary<string, object>), typeof(object))]
+ [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary<string, JsonElement>), typeof(JsonElement))]
+ [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<object>), typeof(object))]
+ [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<JsonElement>), typeof(JsonElement))]
+ public void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverter(Type attributedType, Type dictionaryType, Type elementType)
+ {
+ typeof(ExtensionDataTests)
+ .GetMethod(nameof(ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal), BindingFlags.Static | BindingFlags.NonPublic)
+ .MakeGenericMethod(attributedType, dictionaryType, elementType)
+ .Invoke(null, null);
+ }
+
+ [Fact]
+ public async Task ExtensionProperty_IgnoresCustomSerializerWithExplicitConverter_JsonObject()
+ {
+ ClassWithExtensionData<JsonObject> obj
+ = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionData<JsonObject>>(@"{""TestKey"":""TestValue""}");
+
+ Assert.Equal("TestValue", obj.Overflow["TestKey"].GetValue<string>());
+ }
+
+ private static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal<TRoot, TDictionary, TOverflowItem>()
+ where TRoot : IClassWithOverflow<TDictionary>, new()
+ where TDictionary : IDictionary<string, TOverflowItem>
+ {
+ ClassWithExtensionData<TDictionary> obj
+ = JsonSerializer.Deserialize<ClassWithExtensionData<TDictionary>>(@"{""TestKey"":""TestValue""}");
+
+ Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString());
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyObjectValue_Empty()
+ {
+ ClassWithExtensionPropertyAlreadyInstantiated obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
+ Assert.Equal(@"{}", await JsonSerializerWrapperForString.SerializeWrapper(obj));
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyObjectValue_SameAsExtensionPropertyName()
+ {
+ const string json = @"{""MyOverflow"":{""Key1"":""V""}}";
+
+ // Deserializing directly into the overflow is not supported by design.
+ ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+
+ // The JSON is treated as normal overflow.
+ Assert.NotNull(obj.MyOverflow["MyOverflow"]);
+ Assert.Equal(json, await JsonSerializerWrapperForString.SerializeWrapper(obj));
+ }
+
+ public class ClassWithExtensionPropertyAsObjectAndNameProperty
+ {
+ public string Name { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow { get; set; }
+ }
+
+ public static IEnumerable<object[]> JsonSerializerOptions()
+ {
+ yield return new object[] { null };
+ yield return new object[] { new JsonSerializerOptions() };
+ yield return new object[] { new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement } };
+ yield return new object[] { new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode } };
+ }
+
+ [Theory]
+ [MemberData(nameof(JsonSerializerOptions))]
+ public async Task ExtensionPropertyDuplicateNames(JsonSerializerOptions options)
+ {
+ var obj = new ClassWithExtensionPropertyAsObjectAndNameProperty();
+ obj.Name = "Name1";
+
+ obj.MyOverflow = new Dictionary<string, object>();
+ obj.MyOverflow["Name"] = "Name2";
+
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+ Assert.Equal(@"{""Name"":""Name1"",""Name"":""Name2""}", json);
+
+ // The overflow value comes last in the JSON so it overwrites the original value.
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObjectAndNameProperty>(json, options);
+ Assert.Equal("Name2", obj.Name);
+
+ // Since there was no overflow, this should be null.
+ Assert.Null(obj.MyOverflow);
+ }
+
+ [Theory]
+ [MemberData(nameof(JsonSerializerOptions))]
+ public async Task Null_SystemObject(JsonSerializerOptions options)
+ {
+ const string json = @"{""MissingProperty"":null}";
+
+ {
+ ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json, options);
+
+ // A null value maps to <object>, so the value is null.
+ object elem = obj.MyOverflow["MissingProperty"];
+ Assert.Null(elem);
+ }
+
+ {
+ ClassWithExtensionPropertyAsJsonObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonObject>(json, options);
+
+ JsonObject jObject = obj.MyOverflow;
+ JsonNode jNode = jObject["MissingProperty"];
+ // Since JsonNode is a reference type the value is null.
+ Assert.Null(jNode);
+ }
+ }
+
+ [Fact]
+ public async Task Null_JsonElement()
+ {
+ const string json = @"{""MissingProperty"":null}";
+
+ ClassWithExtensionPropertyAsJsonElement obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+ object elem = obj.MyOverflow["MissingProperty"];
+ // Since JsonElement is a struct, it treats null as JsonValueKind.Null.
+ Assert.IsType<JsonElement>(elem);
+ Assert.Equal(JsonValueKind.Null, ((JsonElement)elem).ValueKind);
+ }
+
+ [Fact]
+ public async Task Null_JsonObject()
+ {
+ const string json = @"{""MissingProperty"":null}";
+
+ ClassWithExtensionPropertyAsJsonObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonObject>(json);
+ object elem = obj.MyOverflow["MissingProperty"];
+ // Since JsonNode is a reference type the value is null.
+ Assert.Null(elem);
+ }
+
+ [Fact]
+ public async Task ExtensionPropertyObjectValue()
+ {
+ // Baseline
+ ClassWithExtensionPropertyAlreadyInstantiated obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
+ obj.MyOverflow.Add("test", new object());
+ obj.MyOverflow.Add("test1", 1);
+
+ Assert.Equal(@"{""test"":{},""test1"":1}", await JsonSerializerWrapperForString.SerializeWrapper(obj));
+ }
+
+ public class DummyObj
+ {
+ public string Prop { get; set; }
+ }
+
+ public struct DummyStruct
+ {
+ public string Prop { get; set; }
+ }
+
+ [Theory]
+ [MemberData(nameof(JsonSerializerOptions))]
+ public async Task ExtensionPropertyObjectValue_RoundTrip(JsonSerializerOptions options)
+ {
+ // Baseline
+ ClassWithExtensionPropertyAlreadyInstantiated obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}", options);
+ obj.MyOverflow.Add("test", new object());
+ obj.MyOverflow.Add("test1", 1);
+ obj.MyOverflow.Add("test2", "text");
+ obj.MyOverflow.Add("test3", new DummyObj() { Prop = "ObjectProp" });
+ obj.MyOverflow.Add("test4", new DummyStruct() { Prop = "StructProp" });
+ obj.MyOverflow.Add("test5", new Dictionary<string, object>() { { "Key", "Value" }, { "Key1", "Value1" }, });
+
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ ClassWithExtensionPropertyAlreadyInstantiated roundTripObj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAlreadyInstantiated>(json, options);
+
+ Assert.Equal(6, roundTripObj.MyOverflow.Count);
+
+ if (options?.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode)
+ {
+ Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test"]);
+ Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test1"]);
+ Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test2"]);
+ Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test3"]);
+
+ Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test"]);
+
+ Assert.IsAssignableFrom<JsonValue>(roundTripObj.MyOverflow["test1"]);
+ Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue<int>());
+ Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue<long>());
+
+ Assert.IsAssignableFrom<JsonValue>(roundTripObj.MyOverflow["test2"]);
+ Assert.Equal("text", ((JsonValue)roundTripObj.MyOverflow["test2"]).GetValue<string>());
+
+ Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test3"]);
+ Assert.Equal("ObjectProp", ((JsonObject)roundTripObj.MyOverflow["test3"])["Prop"].GetValue<string>());
+
+ Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test4"]);
+ Assert.Equal("StructProp", ((JsonObject)roundTripObj.MyOverflow["test4"])["Prop"].GetValue<string>());
+
+ Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test5"]);
+ Assert.Equal("Value", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key"].GetValue<string>());
+ Assert.Equal("Value1", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key1"].GetValue<string>());
+ }
+ else
+ {
+ Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test"]);
+ Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test1"]);
+ Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test2"]);
+ Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test3"]);
+
+ Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test"]).ValueKind);
+
+ Assert.Equal(JsonValueKind.Number, ((JsonElement)roundTripObj.MyOverflow["test1"]).ValueKind);
+ Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt32());
+ Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt64());
+
+ Assert.Equal(JsonValueKind.String, ((JsonElement)roundTripObj.MyOverflow["test2"]).ValueKind);
+ Assert.Equal("text", ((JsonElement)roundTripObj.MyOverflow["test2"]).GetString());
+
+ Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test3"]).ValueKind);
+ Assert.Equal("ObjectProp", ((JsonElement)roundTripObj.MyOverflow["test3"]).GetProperty("Prop").GetString());
+
+ Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test4"]).ValueKind);
+ Assert.Equal("StructProp", ((JsonElement)roundTripObj.MyOverflow["test4"]).GetProperty("Prop").GetString());
+
+ Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test5"]).ValueKind);
+ Assert.Equal("Value", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key").GetString());
+ Assert.Equal("Value1", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key1").GetString());
+ }
+ }
+
+ [Fact]
+ public async Task DeserializeIntoJsonObjectProperty()
+ {
+ string json = @"{""MyDict"":{""Property1"":1}}";
+ ClassWithExtensionPropertyAsJsonObject obj =
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonObject>(json);
+
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(1, obj.MyOverflow["MyDict"]["Property1"].GetValue<int>());
+ }
+
+ [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
+#endif
+
+ public async Task DeserializeIntoSystemObjectProperty()
+ {
+ string json = @"{""MyDict"":{""Property1"":1}}";
+
+ await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsSystemObject>(json));
+
+ // Cannot deserialize into System.Object overflow even if UnknownTypeHandling is set to use JsonNode.
+ var options = new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode };
+ await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsSystemObject>(json));
+ }
+
+ public class ClassWithReference
+ {
+ [JsonExtensionData]
+ public Dictionary<string, JsonElement> MyOverflow { get; set; }
+
+ public ClassWithExtensionProperty MyReference { get; set; }
+ }
+
+ [Theory]
+ [InlineData(@"{""MyIntMissing"":2,""MyReference"":{""MyIntMissingChild"":3}}")]
+ [InlineData(@"{""MyReference"":{""MyIntMissingChild"":3},""MyIntMissing"":2}")]
+ [InlineData(@"{""MyReference"":{""MyNestedClass"":null,""MyInt"":0,""MyIntMissingChild"":3},""MyIntMissing"":2}")]
+ public async Task NestedClass(string json)
+ {
+ ClassWithReference obj;
+
+ void Verify()
+ {
+ Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+
+ ClassWithExtensionProperty child = obj.MyReference;
+
+ Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
+ Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
+ Assert.Equal(1, child.MyOverflow.Count);
+ Assert.Equal(3, child.MyOverflow["MyIntMissingChild"].GetInt32());
+ Assert.Null(child.MyNestedClass);
+ Assert.Equal(0, child.MyInt);
+ }
+
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithReference>(json);
+ Verify();
+
+ // Round-trip the json and verify.
+ json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithReference>(json);
+ Verify();
+ }
+
+ public class ParentClassWithObject
+ {
+ public string Text { get; set; }
+ public ChildClassWithObject Child { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
+ }
+
+ public class ChildClassWithObject
+ {
+ public int Number { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
+ }
+
+ [Fact]
+ public async Task NestedClassWithObjectExtensionDataProperty()
+ {
+ var child = new ChildClassWithObject { Number = 2 };
+ child.ExtensionData.Add("SpecialInformation", "I am child class");
+
+ var parent = new ParentClassWithObject { Text = "Hello World" };
+ parent.ExtensionData.Add("SpecialInformation", "I am parent class");
+ parent.Child = child;
+
+ // The extension data is based on the raw strings added above and not JsonElement.
+ Assert.Equal("Hello World", parent.Text);
+ Assert.IsType<string>(parent.ExtensionData["SpecialInformation"]);
+ Assert.Equal("I am parent class", (string)parent.ExtensionData["SpecialInformation"]);
+ Assert.Equal(2, parent.Child.Number);
+ Assert.IsType<string>(parent.Child.ExtensionData["SpecialInformation"]);
+ Assert.Equal("I am child class", (string)parent.Child.ExtensionData["SpecialInformation"]);
+
+ // Round-trip and verify. Extension data is now based on JsonElement.
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(parent);
+ parent = await JsonSerializerWrapperForString.DeserializeWrapper<ParentClassWithObject>(json);
+
+ Assert.Equal("Hello World", parent.Text);
+ Assert.IsType<JsonElement>(parent.ExtensionData["SpecialInformation"]);
+ Assert.Equal("I am parent class", ((JsonElement)parent.ExtensionData["SpecialInformation"]).GetString());
+ Assert.Equal(2, parent.Child.Number);
+ Assert.IsType<JsonElement>(parent.Child.ExtensionData["SpecialInformation"]);
+ Assert.Equal("I am child class", ((JsonElement)parent.Child.ExtensionData["SpecialInformation"]).GetString());
+ }
+
+ public class ParentClassWithJsonElement
+ {
+ public string Text { get; set; }
+
+ public List<ChildClassWithJsonElement> Children { get; set; } = new List<ChildClassWithJsonElement>();
+
+ [JsonExtensionData]
+ // Use SortedDictionary as verification of supporting derived dictionaries.
+ public SortedDictionary<string, JsonElement> ExtensionData { get; set; } = new SortedDictionary<string, JsonElement>();
+ }
+
+ public class ChildClassWithJsonElement
+ {
+ public int Number { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, JsonElement> ExtensionData { get; set; } = new Dictionary<string, JsonElement>();
+ }
+
+ [Fact]
+ public async Task NestedClassWithJsonElementExtensionDataProperty()
+ {
+ var child = new ChildClassWithJsonElement { Number = 4 };
+ child.ExtensionData.Add("SpecialInformation", JsonDocument.Parse(await JsonSerializerWrapperForString.SerializeWrapper("I am child class")).RootElement);
+
+ var parent = new ParentClassWithJsonElement { Text = "Hello World" };
+ parent.ExtensionData.Add("SpecialInformation", JsonDocument.Parse(await JsonSerializerWrapperForString.SerializeWrapper("I am parent class")).RootElement);
+ parent.Children.Add(child);
+
+ Verify();
+
+ // Round-trip and verify.
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(parent);
+ parent = await JsonSerializerWrapperForString.DeserializeWrapper<ParentClassWithJsonElement>(json);
+ Verify();
+
+ void Verify()
+ {
+ Assert.Equal("Hello World", parent.Text);
+ Assert.Equal("I am parent class", parent.ExtensionData["SpecialInformation"].GetString());
+ Assert.Equal(1, parent.Children.Count);
+ Assert.Equal(4, parent.Children[0].Number);
+ Assert.Equal("I am child class", parent.Children[0].ExtensionData["SpecialInformation"].GetString());
+ }
+ }
+
+ [Fact]
+ public async Task DeserializeIntoObjectProperty()
+ {
+ ClassWithExtensionPropertyAsObject obj;
+ string json;
+
+ // Baseline dictionary.
+ json = @"{""MyDict"":{""Property1"":1}}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyDict"]).EnumerateObject().First().Value.GetInt32());
+
+ // Attempt to deserialize directly into the overflow property; this is just added as a normal missing property like MyDict above.
+ json = @"{""MyOverflow"":{""Property1"":1}}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyOverflow"]).EnumerateObject().First().Value.GetInt32());
+
+ // Attempt to deserialize null into the overflow property. This is also treated as a missing property.
+ json = @"{""MyOverflow"":null}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Null(obj.MyOverflow["MyOverflow"]);
+
+ // Attempt to deserialize object into the overflow property. This is also treated as a missing property.
+ json = @"{""MyOverflow"":{}}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(JsonValueKind.Object, ((JsonElement)obj.MyOverflow["MyOverflow"]).ValueKind);
+ }
+
+ [Fact]
+ public async Task DeserializeIntoMultipleDictionaries()
+ {
+ ClassWithMultipleDictionaries obj;
+ string json;
+
+ // Baseline dictionary.
+ json = @"{""ActualDictionary"":{""Key"": {""Property0"":-1}},""MyDict"":{""Property1"":1}}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithMultipleDictionaries>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyDict"]).EnumerateObject().First().Value.GetInt32());
+ Assert.Equal(1, obj.ActualDictionary.Count);
+ Assert.Equal(-1, ((JsonElement)obj.ActualDictionary["Key"]).EnumerateObject().First().Value.GetInt32());
+
+ // Attempt to deserialize null into the dictionary and overflow property. This is also treated as a missing property.
+ json = @"{""ActualDictionary"":null,""MyOverflow"":null}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithMultipleDictionaries>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Null(obj.MyOverflow["MyOverflow"]);
+ Assert.Null(obj.ActualDictionary);
+
+ // Attempt to deserialize object into the dictionary and overflow property. This is also treated as a missing property.
+ json = @"{""ActualDictionary"":{},""MyOverflow"":{}}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithMultipleDictionaries>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(JsonValueKind.Object, ((JsonElement)obj.MyOverflow["MyOverflow"]).ValueKind);
+ Assert.Equal(0, obj.ActualDictionary.Count);
+ }
+
+ [Fact]
+ public async Task DeserializeIntoJsonElementProperty()
+ {
+ ClassWithExtensionPropertyAsJsonElement obj;
+ string json;
+
+ // Baseline dictionary.
+ json = @"{""MyDict"":{""Property1"":1}}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(1, obj.MyOverflow["MyDict"].EnumerateObject().First().Value.GetInt32());
+
+ // Attempt to deserialize directly into the overflow property; this is just added as a normal missing property like MyDict above.
+ json = @"{""MyOverflow"":{""Property1"":1}}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(1, obj.MyOverflow["MyOverflow"].EnumerateObject().First().Value.GetInt32());
+
+ // Attempt to deserialize null into the overflow property. This is also treated as a missing property.
+ json = @"{""MyOverflow"":null}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyOverflow"].ValueKind);
+
+ // Attempt to deserialize object into the overflow property. This is also treated as a missing property.
+ json = @"{""MyOverflow"":{}}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(json);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(JsonValueKind.Object, obj.MyOverflow["MyOverflow"].ValueKind);
+ }
+
+ [Fact]
+ public async Task SerializerOutputRoundtripsWhenEscaping()
+ {
+ string jsonString = "{\"\u6C49\u5B57\":\"abc\",\"Class\":{\"\u6F22\u5B57\":\"xyz\"},\"\u62DC\u6258\":{\"\u62DC\u6258\u62DC\u6258\":1}}";
+
+ ClassWithEscapedProperty input = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEscapedProperty>(jsonString);
+
+ Assert.Equal("abc", input.\u6C49\u5B57);
+ Assert.Equal("xyz", input.Class.\u6F22\u5B57);
+
+ string normalizedString = await JsonSerializerWrapperForString.SerializeWrapper(input);
+
+ Assert.Equal(normalizedString, await JsonSerializerWrapperForString.SerializeWrapper(await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEscapedProperty>(normalizedString)));
+ }
+
+ public class ClassWithEscapedProperty
+ {
+ public string \u6C49\u5B57 { get; set; }
+ public NestedClassWithEscapedProperty Class { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, object> Overflow { get; set; }
+ }
+
+ public class NestedClassWithEscapedProperty
+ {
+ public string \u6F22\u5B57 { get; set; }
+ }
+
+ public class ClassWithInvalidExtensionPropertyStringString
+ {
+ [JsonExtensionData]
+ public Dictionary<string, string> MyOverflow { get; set; }
+ }
+
+ public class ClassWithInvalidExtensionPropertyObjectString
+ {
+ [JsonExtensionData]
+ public Dictionary<DummyObj, string> MyOverflow { get; set; }
+ }
+
+ public class ClassWithInvalidExtensionPropertyStringJsonNode
+ {
+ [JsonExtensionData]
+ public Dictionary<string, JsonNode> MyOverflow { get; set; }
+ }
+
+ [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
+#endif
+ public async Task ExtensionProperty_InvalidDictionary()
+ {
+ var obj1 = new ClassWithInvalidExtensionPropertyStringString();
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj1));
+
+ var obj2 = new ClassWithInvalidExtensionPropertyObjectString();
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj2));
+
+ var obj3 = new ClassWithInvalidExtensionPropertyStringJsonNode();
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj3));
+ }
+
+ public class ClassWithExtensionPropertyAlreadyInstantiated
+ {
+ public ClassWithExtensionPropertyAlreadyInstantiated()
+ {
+ MyOverflow = new Dictionary<string, object>();
+ }
+
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyAsObject
+ {
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyAsJsonElement
+ {
+ [JsonExtensionData]
+ public Dictionary<string, JsonElement> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyAsJsonObject
+ {
+ [JsonExtensionData]
+ public JsonObject MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyAsSystemObject
+ {
+ [JsonExtensionData]
+ public object MyOverflow { get; set; }
+ }
+
+ public class ClassWithMultipleDictionaries
+ {
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow { get; set; }
+
+ public Dictionary<string, object> ActualDictionary { get; set; }
+ }
+
+ [Fact]
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
+#endif
+ public async Task DeserializeIntoImmutableDictionaryProperty()
+ {
+ // baseline
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsImmutable>(@"{}");
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsImmutableJsonElement>(@"{}");
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyPrivateConstructor>(@"{}");
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyPrivateConstructorJsonElement>(@"{}");
+
+ await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsImmutable>("{\"hello\":\"world\"}"));
+ await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsImmutableJsonElement>("{\"hello\":\"world\"}"));
+ await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyPrivateConstructor>("{\"hello\":\"world\"}"));
+ await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyPrivateConstructorJsonElement>("{\"hello\":\"world\"}"));
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyCustomIImmutable>("{\"hello\":\"world\"}"));
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyCustomIImmutableJsonElement>("{\"hello\":\"world\"}"));
+ }
+
+ [Fact]
+ public async Task SerializeIntoImmutableDictionaryProperty()
+ {
+ // attempt to serialize a null immutable dictionary
+ string expectedJson = "{}";
+ var obj = new ClassWithExtensionPropertyAsImmutable();
+ var json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal(expectedJson, json);
+
+ // attempt to serialize an empty immutable dictionary
+ expectedJson = "{}";
+ obj = new ClassWithExtensionPropertyAsImmutable();
+ obj.MyOverflow = ImmutableDictionary<string, object>.Empty;
+ json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal(expectedJson, json);
+
+ // attempt to serialize a populated immutable dictionary
+ expectedJson = "{\"hello\":\"world\"}";
+ obj = new ClassWithExtensionPropertyAsImmutable();
+ var dictionaryStringObject = new Dictionary<string, object> { { "hello", "world" } };
+ obj.MyOverflow = ImmutableDictionary.CreateRange(dictionaryStringObject);
+ json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal(expectedJson, json);
+ }
+
+ public class ClassWithExtensionPropertyAsImmutable
+ {
+ [JsonExtensionData]
+ public ImmutableDictionary<string, object> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyAsImmutableJsonElement
+ {
+ [JsonExtensionData]
+ public ImmutableDictionary<string, JsonElement> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyPrivateConstructor
+ {
+ [JsonExtensionData]
+ public GenericIDictionaryWrapperPrivateConstructor<string, object> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyPrivateConstructorJsonElement
+ {
+ [JsonExtensionData]
+ public GenericIDictionaryWrapperPrivateConstructor<string, JsonElement> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyCustomIImmutable
+ {
+ [JsonExtensionData]
+ public GenericIImmutableDictionaryWrapper<string, object> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyCustomIImmutableJsonElement
+ {
+ [JsonExtensionData]
+ public GenericIImmutableDictionaryWrapper<string, JsonElement> MyOverflow { get; set; }
+ }
+
+ [Theory]
+ [InlineData(typeof(ClassWithExtensionPropertyNoGenericParameters))]
+ [InlineData(typeof(ClassWithExtensionPropertyOneGenericParameter))]
+ [InlineData(typeof(ClassWithExtensionPropertyThreeGenericParameters))]
+ public async Task DeserializeIntoGenericDictionaryParameterCount(Type type)
+ {
+ object obj = await JsonSerializerWrapperForString.DeserializeWrapper("{\"hello\":\"world\"}", type);
+
+ IDictionary<string, object> extData = (IDictionary<string, object>)type.GetProperty("MyOverflow").GetValue(obj)!;
+ Assert.Equal("world", ((JsonElement)extData["hello"]).GetString());
+ }
+
+ public class ClassWithExtensionPropertyNoGenericParameters
+ {
+ [JsonExtensionData]
+ public StringToObjectIDictionaryWrapper MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyOneGenericParameter
+ {
+ [JsonExtensionData]
+ public StringToGenericIDictionaryWrapper<object> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyThreeGenericParameters
+ {
+ [JsonExtensionData]
+ public GenericIDictonaryWrapperThreeGenericParameters<string, object, string> MyOverflow { get; set; }
+ }
+
+ [Fact]
+ public async Task CustomObjectConverterInExtensionProperty()
+ {
+ const string Json = "{\"hello\": \"world\"}";
+
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new ObjectConverter());
+
+ ClassWithExtensionPropertyAsObject obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsObject>(Json, options);
+ object overflowProp = obj.MyOverflow["hello"];
+ Assert.IsType<string>(overflowProp);
+ Assert.Equal("world!!!", ((string)overflowProp));
+
+ string newJson = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+ Assert.Equal("{\"hello\":\"world!!!\"}", newJson);
+ }
+
+ public class ObjectConverter : JsonConverter<object>
+ {
+ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return reader.GetString() + "!!!";
+ }
+
+ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
+ {
+ // Since we are in a user-provided (not internal to S.T.Json) object converter,
+ // this converter will be called, not the internal string converter.
+ writer.WriteStringValue((string)value);
+ }
+ }
+
+ [Fact]
+ public async Task CustomJsonElementConverterInExtensionProperty()
+ {
+ const string Json = "{\"hello\": \"world\"}";
+
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new JsonElementConverter());
+
+ ClassWithExtensionPropertyAsJsonElement obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonElement>(Json, options);
+ JsonElement overflowProp = obj.MyOverflow["hello"];
+ Assert.Equal(JsonValueKind.Undefined, overflowProp.ValueKind);
+
+ string newJson = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+ Assert.Equal("{\"hello\":{\"Hi\":\"There\"}}", newJson);
+ }
+
+ public class JsonElementConverter : JsonConverter<JsonElement>
+ {
+ public override JsonElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ // Just return an empty JsonElement.
+ reader.Skip();
+ return new JsonElement();
+ }
+
+ public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
+ {
+ // Write a string we can test against easily.
+ writer.WriteStartObject();
+ writer.WriteString("Hi", "There");
+ writer.WriteEndObject();
+ }
+ }
+
+ [Fact]
+ public async Task CustomJsonObjectConverterInExtensionProperty()
+ {
+ const string Json = "{\"hello\": \"world\"}";
+
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new JsonObjectConverter());
+
+ // A custom converter for JsonObject is not allowed on an extension property.
+ InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+ await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithExtensionPropertyAsJsonObject>(Json, options));
+
+ Assert.Contains("JsonObject", ex.ToString());
+ }
+
+ public class JsonObjectConverter : JsonConverter<JsonObject>
+ {
+ public override JsonObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ // Just return an empty JsonElement.
+ reader.Skip();
+ return new JsonObject();
+ }
+
+ public override void Write(Utf8JsonWriter writer, JsonObject value, JsonSerializerOptions options)
+ {
+ // Write a string we can test against easily.
+ writer.WriteStartObject();
+ writer.WriteString("Hi", "There");
+ writer.WriteEndObject();
+ }
+ }
+
+ [Fact]
+ public async Task EmptyPropertyAndExtensionData_PropertyFirst()
+ {
+ // Verify any caching treats real property (with empty name) differently than a missing property.
+
+ ClassWithEmptyPropertyNameAndExtensionProperty obj;
+
+ // Create a new options instances to re-set any caches.
+ JsonSerializerOptions options = new JsonSerializerOptions();
+
+ // First use an empty property.
+ string json = @"{"""":43}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+ Assert.Equal(43, obj.MyInt1);
+ Assert.Null(obj.MyOverflow);
+
+ // Then populate cache with a missing property name.
+ json = @"{""DoesNotExist"":42}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+ Assert.Equal(0, obj.MyInt1);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(42, obj.MyOverflow["DoesNotExist"].GetInt32());
+ }
+
+ [Fact]
+ public async Task EmptyPropertyNameAndExtensionData_ExtDataFirst()
+ {
+ // Verify any caching treats real property (with empty name) differently than a missing property.
+
+ ClassWithEmptyPropertyNameAndExtensionProperty obj;
+
+ // Create a new options instances to re-set any caches.
+ JsonSerializerOptions options = new JsonSerializerOptions();
+
+ // First populate cache with a missing property name.
+ string json = @"{""DoesNotExist"":42}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+ Assert.Equal(0, obj.MyInt1);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(42, obj.MyOverflow["DoesNotExist"].GetInt32());
+
+ // Then use an empty property.
+ json = @"{"""":43}";
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
+ Assert.Equal(43, obj.MyInt1);
+ Assert.Null(obj.MyOverflow);
+ }
+
+ [Fact]
+ public async Task ExtensionDataDictionarySerialize_DoesNotHonor()
+ {
+ var options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
+ EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<EmptyClassWithExtensionProperty>(@"{""Key1"": 1}", options);
+
+ // Ignore naming policy for extension data properties by default.
+ Assert.False(obj.MyOverflow.ContainsKey("key1"));
+ Assert.Equal(1, obj.MyOverflow["Key1"].GetInt32());
+ }
+
+ [Theory]
+ [InlineData(0x1, 'v')]
+ [InlineData(0x1, '\u0467')]
+ [InlineData(0x10, 'v')]
+ [InlineData(0x10, '\u0467')]
+ [InlineData(0x100, 'v')]
+ [InlineData(0x100, '\u0467')]
+ [InlineData(0x1000, 'v')]
+ [InlineData(0x1000, '\u0467')]
+ [InlineData(0x10000, 'v')]
+ [InlineData(0x10000, '\u0467')]
+ public async Task LongPropertyNames(int propertyLength, char ch)
+ {
+ // Although the CLR may limit member length to 1023 bytes, the serializer doesn't have a hard limit.
+
+ string val = new string(ch, propertyLength);
+ string json = @"{""" + val + @""":1}";
+
+ EmptyClassWithExtensionProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper<EmptyClassWithExtensionProperty>(json);
+
+ Assert.True(obj.MyOverflow.ContainsKey(val));
+
+ var options = new JsonSerializerOptions
+ {
+ // Avoid escaping '\u0467'.
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+ };
+
+ string jsonRoundTripped = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+ Assert.Equal(json, jsonRoundTripped);
+ }
+
+ public class EmptyClassWithExtensionProperty
+ {
+ [JsonExtensionData]
+ public IDictionary<string, JsonElement> MyOverflow { get; set; }
+ }
+
+ public class ClassWithEmptyPropertyNameAndExtensionProperty
+ {
+ [JsonPropertyName("")]
+ public int MyInt1 { get; set; }
+
+ [JsonExtensionData]
+ public IDictionary<string, JsonElement> MyOverflow { get; set; }
+ }
+ }
+}
public int Int { get; set; }
}
- public class ClassWithConstructor_SimpleAndComplexParameters : ITestClassWithParameterizedCtor
+ public class ObjWCtorMixedParams : ITestClassWithParameterizedCtor
{
public byte MyByte { get; }
public sbyte MySByte { get; set; }
public ImmutableSortedSet<string> MyStringImmutableSortedSetT { get; }
public List<string> MyListOfNullString { get; }
- public ClassWithConstructor_SimpleAndComplexParameters(
+ public ObjWCtorMixedParams(
byte myByte,
char myChar,
string myString,
MyListOfNullString = myListOfNullString;
}
- public static ClassWithConstructor_SimpleAndComplexParameters GetInstance() =>
- JsonSerializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(s_json);
+ public static ObjWCtorMixedParams GetInstance() =>
+ JsonSerializer.Deserialize<ObjWCtorMixedParams>(s_json);
public static string s_json => $"{{{s_partialJson1},{s_partialJson2}}}";
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 = @"
{
[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))]
[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
{
}
[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))]
[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
{
}
--- /dev/null
+//Licensed to the .NET Foundation under one or more agreements.
+//The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Tests;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+ public sealed partial class ExtensionDataTests_Metadata : ExtensionDataTests
+ {
+ public ExtensionDataTests_Metadata()
+ : base(new StringSerializerWrapper(ExtensionDataTestsContext_Metadata.Default, (options) => new ExtensionDataTestsContext_Metadata(options)))
+ {
+ }
+
+ [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
+ [JsonSerializable(typeof(ClassWithEmptyPropertyNameAndExtensionProperty))]
+ [JsonSerializable(typeof(EmptyClassWithExtensionProperty))]
+ [JsonSerializable(typeof(ClassWithExtensionProperty))]
+ [JsonSerializable(typeof(ClassWithExtensionField))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsObject))]
+ [JsonSerializable(typeof(ClassWithIgnoredData))]
+ [JsonSerializable(typeof(Dictionary<string, object>))]
+ [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
+ [JsonSerializable(typeof(CustomOverflowDictionary<object>))]
+ [JsonSerializable(typeof(CustomOverflowDictionary<JsonElement>))]
+ [JsonSerializable(typeof(ExtensionDataTests))]
+ [JsonSerializable(typeof(DictionaryOverflowConverter))]
+ [JsonSerializable(typeof(JsonElementOverflowConverter))]
+ [JsonSerializable(typeof(CustomObjectDictionaryOverflowConverter))]
+ [JsonSerializable(typeof(CustomJsonElementDictionaryOverflowConverter))]
+ [JsonSerializable(typeof(ClassWithExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(Dictionary<string, object>))]
+ [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
+ [JsonSerializable(typeof(CustomOverflowDictionary<object>))]
+ [JsonSerializable(typeof(CustomOverflowDictionary<JsonElement>))]
+ [JsonSerializable(typeof(ClassWithExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAlreadyInstantiated))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsObjectAndNameProperty))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsJsonObject))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsJsonElement))]
+ [JsonSerializable(typeof(ClassWithReference))]
+ [JsonSerializable(typeof(ParentClassWithObject))]
+ [JsonSerializable(typeof(ParentClassWithJsonElement))]
+ [JsonSerializable(typeof(ClassWithMultipleDictionaries))]
+ [JsonSerializable(typeof(ClassWithEscapedProperty))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsImmutable))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsImmutableJsonElement))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyPrivateConstructor))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyPrivateConstructorJsonElement))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyNoGenericParameters))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyOneGenericParameter))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyThreeGenericParameters))]
+ [JsonSerializable(typeof(JsonElement))]
+ [JsonSerializable(typeof(ClassWithExtensionData<JsonObject>))]
+ [JsonSerializable(typeof(int))]
+ [JsonSerializable(typeof(DummyObj))]
+ [JsonSerializable(typeof(DummyStruct))]
+ internal sealed partial class ExtensionDataTestsContext_Metadata : JsonSerializerContext
+ {
+ }
+ }
+
+ public sealed partial class ExtensionDataTests_Default : ExtensionDataTests
+ {
+ public ExtensionDataTests_Default()
+ : base(new StringSerializerWrapper(ExtensionDataTestsContext_Default.Default, (options) => new ExtensionDataTestsContext_Default(options)))
+ {
+ }
+
+ [JsonSerializable(typeof(ClassWithEmptyPropertyNameAndExtensionProperty))]
+ [JsonSerializable(typeof(EmptyClassWithExtensionProperty))]
+ [JsonSerializable(typeof(ClassWithExtensionProperty))]
+ [JsonSerializable(typeof(ClassWithExtensionField))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsObject))]
+ [JsonSerializable(typeof(ClassWithIgnoredData))]
+ [JsonSerializable(typeof(Dictionary<string, object>))]
+ [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
+ [JsonSerializable(typeof(CustomOverflowDictionary<object>))]
+ [JsonSerializable(typeof(CustomOverflowDictionary<JsonElement>))]
+ [JsonSerializable(typeof(ExtensionDataTests))]
+ [JsonSerializable(typeof(DictionaryOverflowConverter))]
+ [JsonSerializable(typeof(JsonElementOverflowConverter))]
+ [JsonSerializable(typeof(CustomObjectDictionaryOverflowConverter))]
+ [JsonSerializable(typeof(CustomJsonElementDictionaryOverflowConverter))]
+ [JsonSerializable(typeof(ClassWithExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(Dictionary<string, object>))]
+ [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
+ [JsonSerializable(typeof(CustomOverflowDictionary<object>))]
+ [JsonSerializable(typeof(CustomOverflowDictionary<JsonElement>))]
+ [JsonSerializable(typeof(ClassWithExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAlreadyInstantiated))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsObjectAndNameProperty))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsJsonObject))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsJsonElement))]
+ [JsonSerializable(typeof(ClassWithReference))]
+ [JsonSerializable(typeof(ParentClassWithObject))]
+ [JsonSerializable(typeof(ParentClassWithJsonElement))]
+ [JsonSerializable(typeof(ClassWithMultipleDictionaries))]
+ [JsonSerializable(typeof(ClassWithEscapedProperty))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsImmutable))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyAsImmutableJsonElement))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyPrivateConstructor))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyPrivateConstructorJsonElement))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyNoGenericParameters))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyOneGenericParameter))]
+ [JsonSerializable(typeof(ClassWithExtensionPropertyThreeGenericParameters))]
+ [JsonSerializable(typeof(JsonElement))]
+ [JsonSerializable(typeof(ClassWithExtensionData<JsonObject>))]
+ [JsonSerializable(typeof(int))]
+ [JsonSerializable(typeof(DummyObj))]
+ [JsonSerializable(typeof(DummyStruct))]
+ internal sealed partial class ExtensionDataTestsContext_Default : JsonSerializerContext
+ {
+ }
+ }
+}
<Compile Include="..\Common\ConstructorTests\ConstructorTests.Exceptions.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.Exceptions.cs" />
<Compile Include="..\Common\ConstructorTests\ConstructorTests.ParameterMatching.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.ParameterMatching.cs" />
<Compile Include="..\Common\ConstructorTests\ConstructorTests.Stream.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.Stream.cs" />
+ <Compile Include="..\Common\ExtensionDataTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ExtensionDataTests.cs" />
<Compile Include="..\Common\JsonSerializerWrapperForStream.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapperForStream.cs" />
<Compile Include="..\Common\JsonSerializerWrapperForString.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapperForString.cs" />
<Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />
<Compile Include="JsonSerializerContextTests.cs" />
<Compile Include="Serialization\CollectionTests.cs" />
<Compile Include="Serialization\ConstructorTests.cs" />
+ <Compile Include="Serialization\ExtensionDataTests.cs" />
<Compile Include="Serialization\JsonSerializerWrapper.cs" />
<Compile Include="JsonTestHelper.cs" />
<Compile Include="MetadataAndSerializationContextTests.cs" />
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Reflection;
-using System.Text.Encodings.Web;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using Xunit;
-
namespace System.Text.Json.Serialization.Tests
{
- public static class ExtensionDataTests
+ public sealed partial class ExtensionDataTestsDynamic : ExtensionDataTests
{
- [Fact]
- public static void EmptyPropertyName_WinsOver_ExtensionDataEmptyPropertyName()
- {
- string json = @"{"""":1}";
-
- ClassWithEmptyPropertyNameAndExtensionProperty obj;
-
- // Create a new options instances to re-set any caches.
- JsonSerializerOptions options = new JsonSerializerOptions();
-
- // Verify the real property wins over the extension data property.
- obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
- Assert.Equal(1, obj.MyInt1);
- Assert.Null(obj.MyOverflow);
- }
-
- [Fact]
- public static void EmptyPropertyNameInExtensionData()
- {
- {
- string json = @"{"""":42}";
- EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize<EmptyClassWithExtensionProperty>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(42, obj.MyOverflow[""].GetInt32());
- }
-
- {
- // Verify that last-in wins.
- string json = @"{"""":42, """":43}";
- EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize<EmptyClassWithExtensionProperty>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(43, obj.MyOverflow[""].GetInt32());
- }
- }
-
- [Fact]
- public static void ExtensionPropertyNotUsed()
- {
- string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
- ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
- Assert.Null(obj.MyOverflow);
- }
-
- [Fact]
- public static void ExtensionPropertyRoundTrip()
- {
- ClassWithExtensionProperty obj;
-
- {
- string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
- Verify();
- }
-
- // Round-trip the json.
- {
- string json = JsonSerializer.Serialize(obj);
- obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
- Verify();
-
- // The json should not contain the dictionary name.
- Assert.DoesNotContain(nameof(ClassWithExtensionProperty.MyOverflow), json);
- }
-
- void Verify()
- {
- Assert.NotNull(obj.MyOverflow);
- Assert.Equal(1, obj.MyInt);
- Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
-
- JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
-
- // Verify a couple properties
- Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
- Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
- }
- }
-
- [Fact]
- public static void ExtensionFieldNotUsed()
- {
- string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
- ClassWithExtensionField obj = JsonSerializer.Deserialize<ClassWithExtensionField>(json);
- Assert.Null(obj.MyOverflow);
- }
-
- [Fact]
- public static void ExtensionFieldRoundTrip()
- {
- ClassWithExtensionField obj;
-
- {
- string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionField>(json);
- Verify();
- }
-
- // Round-trip the json.
- {
- string json = JsonSerializer.Serialize(obj);
- obj = JsonSerializer.Deserialize<ClassWithExtensionField>(json);
- Verify();
-
- // The json should not contain the dictionary name.
- Assert.DoesNotContain(nameof(ClassWithExtensionField.MyOverflow), json);
- }
-
- void Verify()
- {
- Assert.NotNull(obj.MyOverflow);
- Assert.Equal(1, obj.MyInt);
- Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
-
- JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
-
- // Verify a couple properties
- Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
- Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
- }
- }
-
- [Fact]
- public static void ExtensionPropertyIgnoredWhenWritingDefault()
- {
- string expected = @"{}";
- string actual = JsonSerializer.Serialize(new ClassWithExtensionPropertyAsObject());
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public static void MultipleExtensionPropertyIgnoredWhenWritingDefault()
- {
- var obj = new ClassWithMultipleDictionaries();
- string actual = JsonSerializer.Serialize(obj);
- Assert.Equal("{\"ActualDictionary\":null}", actual);
-
- obj = new ClassWithMultipleDictionaries
- {
- ActualDictionary = new Dictionary<string, object>()
- };
- actual = JsonSerializer.Serialize(obj);
- Assert.Equal("{\"ActualDictionary\":{}}", actual);
-
- obj = new ClassWithMultipleDictionaries
- {
- MyOverflow = new Dictionary<string, object>
- {
- { "test", "value" }
- }
- };
- actual = JsonSerializer.Serialize(obj);
- Assert.Equal("{\"ActualDictionary\":null,\"test\":\"value\"}", actual);
-
- obj = new ClassWithMultipleDictionaries
- {
- ActualDictionary = new Dictionary<string, object>(),
- MyOverflow = new Dictionary<string, object>
- {
- { "test", "value" }
- }
- };
- actual = JsonSerializer.Serialize(obj);
- Assert.Equal("{\"ActualDictionary\":{},\"test\":\"value\"}", actual);
- }
-
- [Fact]
- public static void ExtensionPropertyInvalidJsonFail()
- {
- const string BadJson = @"{""Good"":""OK"",""Bad"":!}";
-
- JsonException jsonException = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(BadJson));
- Assert.Contains("Path: $.Bad | LineNumber: 0 | BytePositionInLine: 19.", jsonException.ToString());
- Assert.NotNull(jsonException.InnerException);
- Assert.IsAssignableFrom<JsonException>(jsonException.InnerException);
- Assert.Contains("!", jsonException.InnerException.ToString());
- }
-
- [Fact]
- public static void ExtensionPropertyAlreadyInstantiated()
- {
- Assert.NotNull(new ClassWithExtensionPropertyAlreadyInstantiated().MyOverflow);
-
- string json = @"{""MyIntMissing"":2}";
-
- ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
- Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
- }
-
- [Fact]
- public static void ExtensionPropertyAsObject()
- {
- string json = @"{""MyIntMissing"":2}";
-
- ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
- Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
- Assert.Equal(2, ((JsonElement)obj.MyOverflow["MyIntMissing"]).GetInt32());
- }
-
- [Fact]
- public static void ExtensionPropertyCamelCasing()
- {
- // Currently we apply no naming policy. If we do (such as a ExtensionPropertyNamingPolicy), we'd also have to add functionality to the JsonDocument.
-
- ClassWithExtensionProperty obj;
- const string jsonWithProperty = @"{""MyIntMissing"":1}";
- const string jsonWithPropertyCamelCased = @"{""myIntMissing"":1}";
-
- {
- // Baseline Pascal-cased json + no casing option.
- obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonWithProperty);
- Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
- string json = JsonSerializer.Serialize(obj);
- Assert.Contains(@"""MyIntMissing"":1", json);
- }
-
- {
- // Pascal-cased json + camel casing option.
- JsonSerializerOptions options = new JsonSerializerOptions();
- options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
- options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
-
- obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonWithProperty, options);
- Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
- string json = JsonSerializer.Serialize(obj, options);
- Assert.Contains(@"""MyIntMissing"":1", json);
- }
-
- {
- // Baseline camel-cased json + no casing option.
- obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonWithPropertyCamelCased);
- Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
- string json = JsonSerializer.Serialize(obj);
- Assert.Contains(@"""myIntMissing"":1", json);
- }
-
- {
- // Baseline camel-cased json + camel casing option.
- JsonSerializerOptions options = new JsonSerializerOptions();
- options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
- options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
-
- obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonWithPropertyCamelCased, options);
- Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
- string json = JsonSerializer.Serialize(obj, options);
- Assert.Contains(@"""myIntMissing"":1", json);
- }
- }
-
- [Fact]
- public static void NullValuesIgnored()
- {
- const string json = @"{""MyNestedClass"":null}";
- const string jsonMissing = @"{""MyNestedClassMissing"":null}";
-
- {
- // Baseline with no missing.
- ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(json);
- Assert.Null(obj.MyOverflow);
-
- string outJson = JsonSerializer.Serialize(obj);
- Assert.Contains(@"""MyNestedClass"":null", outJson);
- }
-
- {
- // Baseline with missing.
- ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonMissing);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyNestedClassMissing"].ValueKind);
- }
-
- {
- JsonSerializerOptions options = new JsonSerializerOptions();
- options.IgnoreNullValues = true;
-
- ClassWithExtensionProperty obj = JsonSerializer.Deserialize<ClassWithExtensionProperty>(jsonMissing, options);
-
- // Currently we do not ignore nulls in the extension data. The JsonDocument would also need to support this mode
- // for any lower-level nulls.
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyNestedClassMissing"].ValueKind);
- }
- }
-
- private class ClassWithInvalidExtensionProperty
- {
- [JsonExtensionData]
- public Dictionary<string, int> MyOverflow { get; set; }
- }
-
- private class ClassWithTwoExtensionProperties
- {
- [JsonExtensionData]
- public Dictionary<string, object> MyOverflow1 { get; set; }
-
- [JsonExtensionData]
- public Dictionary<string, object> MyOverflow2 { get; set; }
- }
-
- [Fact]
- public static void InvalidExtensionPropertyFail()
- {
- // Baseline
- JsonSerializer.Deserialize<ClassWithExtensionProperty>(@"{}");
- JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(@"{}");
-
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithInvalidExtensionProperty>(@"{}"));
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithTwoExtensionProperties>(@"{}"));
- }
-
- private class ClassWithIgnoredData
- {
- [JsonExtensionData]
- public Dictionary<string, object> MyOverflow { get; set; }
-
- [JsonIgnore]
- public int MyInt { get; set; }
- }
-
- [Fact]
- public static void IgnoredDataShouldNotBeExtensionData()
- {
- ClassWithIgnoredData obj = JsonSerializer.Deserialize<ClassWithIgnoredData>(@"{""MyInt"":1}");
-
- Assert.Equal(0, obj.MyInt);
- Assert.Null(obj.MyOverflow);
- }
-
- private class ClassWithExtensionData<T>
- {
- [JsonExtensionData]
- public T Overflow { get; set; }
- }
-
- public class CustomOverflowDictionary<T> : Dictionary<string, T>
- {
- }
-
- public class DictionaryOverflowConverter : JsonConverter<Dictionary<string, object>>
- {
- public override Dictionary<string, object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(Utf8JsonWriter writer, Dictionary<string, object> value, JsonSerializerOptions options)
- {
- writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
- }
- }
-
- public class JsonElementOverflowConverter : JsonConverter<Dictionary<string, JsonElement>>
- {
- public override Dictionary<string, JsonElement> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(Utf8JsonWriter writer, Dictionary<string, JsonElement> value, JsonSerializerOptions options)
- {
- writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
- }
- }
-
- public class JsonObjectOverflowConverter : JsonConverter<JsonObject>
- {
- public override JsonObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(Utf8JsonWriter writer, JsonObject value, JsonSerializerOptions options)
- {
- writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
- }
- }
-
- public class CustomObjectDictionaryOverflowConverter : JsonConverter<CustomOverflowDictionary<object>>
- {
- public override CustomOverflowDictionary<object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary<object> value, JsonSerializerOptions options)
- {
- writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
- }
- }
-
- public class CustomJsonElementDictionaryOverflowConverter : JsonConverter<CustomOverflowDictionary<JsonElement>>
- {
- public override CustomOverflowDictionary<JsonElement> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(Utf8JsonWriter writer, CustomOverflowDictionary<JsonElement> value, JsonSerializerOptions options)
- {
- writer.WriteString("MyCustomOverflowWrite", "OverflowValueWrite");
- }
- }
-
- [Theory]
- [InlineData(typeof(Dictionary<string, object>), typeof(DictionaryOverflowConverter))]
- [InlineData(typeof(Dictionary<string, JsonElement>), typeof(JsonElementOverflowConverter))]
- [InlineData(typeof(CustomOverflowDictionary<object>), typeof(CustomObjectDictionaryOverflowConverter))]
- [InlineData(typeof(CustomOverflowDictionary<JsonElement>), typeof(CustomJsonElementDictionaryOverflowConverter))]
- public static void ExtensionProperty_SupportsWritingToCustomSerializerWithOptions(Type overflowType, Type converterType)
- {
- typeof(ExtensionDataTests)
- .GetMethod(nameof(ExtensionProperty_SupportsWritingToCustomSerializerWithOptionsInternal), BindingFlags.Static | BindingFlags.NonPublic)
- .MakeGenericMethod(overflowType, converterType)
- .Invoke(null, null);
- }
-
- private static void ExtensionProperty_SupportsWritingToCustomSerializerWithOptionsInternal<TDictionary, TConverter>()
- where TDictionary : new()
- where TConverter : JsonConverter, new()
- {
- var root = new ClassWithExtensionData<TDictionary>()
- {
- Overflow = new TDictionary()
- };
-
- var options = new JsonSerializerOptions();
- options.Converters.Add(new TConverter());
-
- string json = JsonSerializer.Serialize(root, options);
- Assert.Equal(@"{""MyCustomOverflowWrite"":""OverflowValueWrite""}", json);
- }
-
- private interface IClassWithOverflow<T>
- {
- public T Overflow { get; set; }
- }
-
- private class ClassWithExtensionDataWithAttributedConverter : IClassWithOverflow<Dictionary<string, object>>
- {
- [JsonExtensionData]
- [JsonConverter(typeof(DictionaryOverflowConverter))]
- public Dictionary<string, object> Overflow { get; set; }
- }
-
- private class ClassWithJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow<Dictionary<string, JsonElement>>
- {
- [JsonExtensionData]
- [JsonConverter(typeof(JsonElementOverflowConverter))]
- public Dictionary<string, JsonElement> Overflow { get; set; }
- }
-
- private class ClassWithCustomElementExtensionDataWithAttributedConverter : IClassWithOverflow<CustomOverflowDictionary<object>>
- {
- [JsonExtensionData]
- [JsonConverter(typeof(CustomObjectDictionaryOverflowConverter))]
- public CustomOverflowDictionary<object> Overflow { get; set; }
- }
-
- private class ClassWithCustomJsonElementExtensionDataWithAttributedConverter : IClassWithOverflow<CustomOverflowDictionary<JsonElement>>
- {
- [JsonExtensionData]
- [JsonConverter(typeof(CustomJsonElementDictionaryOverflowConverter))]
- public CustomOverflowDictionary<JsonElement> Overflow { get; set; }
- }
-
- [Theory]
- [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary<string, object>))]
- [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary<string, JsonElement>))]
- [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<object>))]
- [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<JsonElement>))]
- public static void ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverter(Type attributedType, Type dictionaryType)
- {
- typeof(ExtensionDataTests)
- .GetMethod(nameof(ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverterInternal), BindingFlags.Static | BindingFlags.NonPublic)
- .MakeGenericMethod(attributedType, dictionaryType)
- .Invoke(null, null);
- }
-
- private static void ExtensionProperty_SupportsWritingToCustomSerializerWithExplicitConverterInternal<TRoot, TDictionary>()
- where TRoot : IClassWithOverflow<TDictionary>, new()
- where TDictionary : new()
- {
- var root = new TRoot()
- {
- Overflow = new TDictionary()
- };
-
- string json = JsonSerializer.Serialize(root);
- Assert.Equal(@"{""MyCustomOverflowWrite"":""OverflowValueWrite""}", json);
- }
-
- [Theory]
- [InlineData(typeof(Dictionary<string, object>), typeof(DictionaryOverflowConverter), typeof(object))]
- [InlineData(typeof(Dictionary<string, JsonElement>), typeof(JsonElementOverflowConverter), typeof(JsonElement))]
- [InlineData(typeof(CustomOverflowDictionary<object>), typeof(CustomObjectDictionaryOverflowConverter), typeof(object))]
- [InlineData(typeof(CustomOverflowDictionary<JsonElement>), typeof(CustomJsonElementDictionaryOverflowConverter), typeof(JsonElement))]
- public static void ExtensionProperty_IgnoresCustomSerializerWithOptions(Type overflowType, Type converterType, Type elementType)
- {
- typeof(ExtensionDataTests)
- .GetMethod(nameof(ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal), BindingFlags.Static | BindingFlags.NonPublic)
- .MakeGenericMethod(overflowType, elementType, converterType)
- .Invoke(null, null);
- }
-
- [Fact]
- public static void ExtensionProperty_IgnoresCustomSerializerWithOptions_JsonObject()
- {
- var options = new JsonSerializerOptions();
- options.Converters.Add(new JsonObjectOverflowConverter());
-
- // A custom converter for JsonObject is not allowed on an extension property.
- InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() =>
- JsonSerializer.Deserialize<ClassWithExtensionData<JsonObject>>(@"{""TestKey"":""TestValue""}", options));
-
- Assert.Contains("JsonObject", ex.ToString());
- }
-
- private static void ExtensionProperty_IgnoresCustomSerializerWithOptionsInternal<TDictionary, TOverflowItem, TConverter>()
- where TConverter : JsonConverter, new()
- where TDictionary : IDictionary<string, TOverflowItem>
- {
- var options = new JsonSerializerOptions();
- options.Converters.Add(new TConverter());
-
- ClassWithExtensionData<TDictionary> obj
- = JsonSerializer.Deserialize<ClassWithExtensionData<TDictionary>>(@"{""TestKey"":""TestValue""}", options);
-
- Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString());
- }
-
- [Theory]
- [InlineData(typeof(ClassWithExtensionDataWithAttributedConverter), typeof(Dictionary<string, object>), typeof(object))]
- [InlineData(typeof(ClassWithJsonElementExtensionDataWithAttributedConverter), typeof(Dictionary<string, JsonElement>), typeof(JsonElement))]
- [InlineData(typeof(ClassWithCustomElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<object>), typeof(object))]
- [InlineData(typeof(ClassWithCustomJsonElementExtensionDataWithAttributedConverter), typeof(CustomOverflowDictionary<JsonElement>), typeof(JsonElement))]
- public static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverter(Type attributedType, Type dictionaryType, Type elementType)
- {
- typeof(ExtensionDataTests)
- .GetMethod(nameof(ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal), BindingFlags.Static | BindingFlags.NonPublic)
- .MakeGenericMethod(attributedType, dictionaryType, elementType)
- .Invoke(null, null);
- }
-
- [Fact]
- public static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverter_JsonObject()
- {
- ClassWithExtensionData<JsonObject> obj
- = JsonSerializer.Deserialize<ClassWithExtensionData<JsonObject>>(@"{""TestKey"":""TestValue""}");
-
- Assert.Equal("TestValue", obj.Overflow["TestKey"].GetValue<string>());
- }
-
- private static void ExtensionProperty_IgnoresCustomSerializerWithExplicitConverterInternal<TRoot, TDictionary, TOverflowItem>()
- where TRoot : IClassWithOverflow<TDictionary>, new()
- where TDictionary : IDictionary<string, TOverflowItem>
- {
- ClassWithExtensionData<TDictionary> obj
- = JsonSerializer.Deserialize<ClassWithExtensionData<TDictionary>>(@"{""TestKey"":""TestValue""}");
-
- Assert.Equal("TestValue", ((JsonElement)(object)obj.Overflow["TestKey"]).GetString());
- }
-
- [Fact]
- public static void ExtensionPropertyObjectValue_Empty()
- {
- ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
- Assert.Equal(@"{}", JsonSerializer.Serialize(obj));
- }
-
- [Fact]
- public static void ExtensionPropertyObjectValue_SameAsExtensionPropertyName()
- {
- const string json = @"{""MyOverflow"":{""Key1"":""V""}}";
-
- // Deserializing directly into the overflow is not supported by design.
- ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
-
- // The JSON is treated as normal overflow.
- Assert.NotNull(obj.MyOverflow["MyOverflow"]);
- Assert.Equal(json, JsonSerializer.Serialize(obj));
- }
-
- private class ClassWithExtensionPropertyAsObjectAndNameProperty
- {
- public string Name { get; set; }
-
- [JsonExtensionData]
- public Dictionary<string, object> MyOverflow { get; set; }
- }
-
- public static IEnumerable<object[]> JsonSerializerOptions()
- {
- yield return new object[] { null };
- yield return new object[] { new JsonSerializerOptions() };
- yield return new object[] { new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement } };
- yield return new object[] { new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode } };
- }
-
- [Theory]
- [MemberData(nameof(JsonSerializerOptions))]
- public static void ExtensionPropertyDuplicateNames(JsonSerializerOptions options)
- {
- var obj = new ClassWithExtensionPropertyAsObjectAndNameProperty();
- obj.Name = "Name1";
-
- obj.MyOverflow = new Dictionary<string, object>();
- obj.MyOverflow["Name"] = "Name2";
-
- string json = JsonSerializer.Serialize(obj, options);
- Assert.Equal(@"{""Name"":""Name1"",""Name"":""Name2""}", json);
-
- // The overflow value comes last in the JSON so it overwrites the original value.
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObjectAndNameProperty>(json, options);
- Assert.Equal("Name2", obj.Name);
-
- // Since there was no overflow, this should be null.
- Assert.Null(obj.MyOverflow);
- }
-
- [Theory]
- [MemberData(nameof(JsonSerializerOptions))]
- public static void Null_SystemObject(JsonSerializerOptions options)
- {
- const string json = @"{""MissingProperty"":null}";
-
- {
- ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json, options);
-
- // A null value maps to <object>, so the value is null.
- object elem = obj.MyOverflow["MissingProperty"];
- Assert.Null(elem);
- }
-
- {
- ClassWithExtensionPropertyAsJsonObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonObject>(json, options);
-
- JsonObject jObject = obj.MyOverflow;
- JsonNode jNode = jObject["MissingProperty"];
- // Since JsonNode is a reference type the value is null.
- Assert.Null(jNode);
- }
- }
-
- [Fact]
- public static void Null_JsonElement()
- {
- const string json = @"{""MissingProperty"":null}";
-
- ClassWithExtensionPropertyAsJsonElement obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
- object elem = obj.MyOverflow["MissingProperty"];
- // Since JsonElement is a struct, it treats null as JsonValueKind.Null.
- Assert.IsType<JsonElement>(elem);
- Assert.Equal(JsonValueKind.Null, ((JsonElement)elem).ValueKind);
- }
-
- [Fact]
- public static void Null_JsonObject()
- {
- const string json = @"{""MissingProperty"":null}";
-
- ClassWithExtensionPropertyAsJsonObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonObject>(json);
- object elem = obj.MyOverflow["MissingProperty"];
- // Since JsonNode is a reference type the value is null.
- Assert.Null(elem);
- }
-
- [Fact]
- public static void ExtensionPropertyObjectValue()
- {
- // Baseline
- ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
- obj.MyOverflow.Add("test", new object());
- obj.MyOverflow.Add("test1", 1);
-
- Assert.Equal(@"{""test"":{},""test1"":1}", JsonSerializer.Serialize(obj));
- }
-
- private class DummyObj
- {
- public string Prop { get; set; }
- }
-
- private struct DummyStruct
- {
- public string Prop { get; set; }
- }
-
- [Theory]
- [MemberData(nameof(JsonSerializerOptions))]
- public static void ExtensionPropertyObjectValue_RoundTrip(JsonSerializerOptions options)
- {
- // Baseline
- ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}", options);
- obj.MyOverflow.Add("test", new object());
- obj.MyOverflow.Add("test1", 1);
- obj.MyOverflow.Add("test2", "text");
- obj.MyOverflow.Add("test3", new DummyObj() { Prop = "ObjectProp" });
- obj.MyOverflow.Add("test4", new DummyStruct() { Prop = "StructProp" });
- obj.MyOverflow.Add("test5", new Dictionary<string, object>() { { "Key", "Value" }, { "Key1", "Value1" }, });
-
- string json = JsonSerializer.Serialize(obj);
- ClassWithExtensionPropertyAlreadyInstantiated roundTripObj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAlreadyInstantiated>(json, options);
-
- Assert.Equal(6, roundTripObj.MyOverflow.Count);
-
- if (options?.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode)
- {
- Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test"]);
- Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test1"]);
- Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test2"]);
- Assert.IsAssignableFrom<JsonNode>(roundTripObj.MyOverflow["test3"]);
-
- Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test"]);
-
- Assert.IsAssignableFrom<JsonValue>(roundTripObj.MyOverflow["test1"]);
- Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue<int>());
- Assert.Equal(1, ((JsonValue)roundTripObj.MyOverflow["test1"]).GetValue<long>());
-
- Assert.IsAssignableFrom<JsonValue>(roundTripObj.MyOverflow["test2"]);
- Assert.Equal("text", ((JsonValue)roundTripObj.MyOverflow["test2"]).GetValue<string>());
-
- Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test3"]);
- Assert.Equal("ObjectProp", ((JsonObject)roundTripObj.MyOverflow["test3"])["Prop"].GetValue<string>());
-
- Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test4"]);
- Assert.Equal("StructProp", ((JsonObject)roundTripObj.MyOverflow["test4"])["Prop"].GetValue<string>());
-
- Assert.IsType<JsonObject>(roundTripObj.MyOverflow["test5"]);
- Assert.Equal("Value", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key"].GetValue<string>());
- Assert.Equal("Value1", ((JsonObject)roundTripObj.MyOverflow["test5"])["Key1"].GetValue<string>());
- }
- else
- {
- Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test"]);
- Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test1"]);
- Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test2"]);
- Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test3"]);
-
- Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test"]).ValueKind);
-
- Assert.Equal(JsonValueKind.Number, ((JsonElement)roundTripObj.MyOverflow["test1"]).ValueKind);
- Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt32());
- Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt64());
-
- Assert.Equal(JsonValueKind.String, ((JsonElement)roundTripObj.MyOverflow["test2"]).ValueKind);
- Assert.Equal("text", ((JsonElement)roundTripObj.MyOverflow["test2"]).GetString());
-
- Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test3"]).ValueKind);
- Assert.Equal("ObjectProp", ((JsonElement)roundTripObj.MyOverflow["test3"]).GetProperty("Prop").GetString());
-
- Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test4"]).ValueKind);
- Assert.Equal("StructProp", ((JsonElement)roundTripObj.MyOverflow["test4"]).GetProperty("Prop").GetString());
-
- Assert.Equal(JsonValueKind.Object, ((JsonElement)roundTripObj.MyOverflow["test5"]).ValueKind);
- Assert.Equal("Value", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key").GetString());
- Assert.Equal("Value1", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key1").GetString());
- }
- }
-
- [Fact]
- public static void DeserializeIntoJsonObjectProperty()
- {
- string json = @"{""MyDict"":{""Property1"":1}}";
- ClassWithExtensionPropertyAsJsonObject obj =
- JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonObject>(json);
-
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(1, obj.MyOverflow["MyDict"]["Property1"].GetValue<int>());
- }
-
- [Fact]
- public static void DeserializeIntoSystemObjectProperty()
- {
- string json = @"{""MyDict"":{""Property1"":1}}";
-
- Assert.Throws<InvalidOperationException>(() =>
- JsonSerializer.Deserialize<ClassWithExtensionPropertyAsSystemObject>(json));
-
- // Cannot deserialize into System.Object overflow even if UnknownTypeHandling is set to use JsonNode.
- var options = new JsonSerializerOptions { UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode };
- Assert.Throws<InvalidOperationException>(() =>
- JsonSerializer.Deserialize<ClassWithExtensionPropertyAsSystemObject>(json));
- }
-
- private class ClassWithReference
- {
- [JsonExtensionData]
- public Dictionary<string, JsonElement> MyOverflow { get; set; }
-
- public ClassWithExtensionProperty MyReference { get; set; }
- }
-
- [Theory]
- [InlineData(@"{""MyIntMissing"":2,""MyReference"":{""MyIntMissingChild"":3}}")]
- [InlineData(@"{""MyReference"":{""MyIntMissingChild"":3},""MyIntMissing"":2}")]
- [InlineData(@"{""MyReference"":{""MyNestedClass"":null,""MyInt"":0,""MyIntMissingChild"":3},""MyIntMissing"":2}")]
- public static void NestedClass(string json)
- {
- ClassWithReference obj;
-
- void Verify()
- {
- Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
-
- ClassWithExtensionProperty child = obj.MyReference;
-
- Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
- Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
- Assert.Equal(1, child.MyOverflow.Count);
- Assert.Equal(3, child.MyOverflow["MyIntMissingChild"].GetInt32());
- Assert.Null(child.MyNestedClass);
- Assert.Equal(0, child.MyInt);
- }
-
- obj = JsonSerializer.Deserialize<ClassWithReference>(json);
- Verify();
-
- // Round-trip the json and verify.
- json = JsonSerializer.Serialize(obj);
- obj = JsonSerializer.Deserialize<ClassWithReference>(json);
- Verify();
- }
-
- private class ParentClassWithObject
- {
- public string Text { get; set; }
- public ChildClassWithObject Child { get; set; }
-
- [JsonExtensionData]
- public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
- }
-
- private class ChildClassWithObject
- {
- public int Number { get; set; }
-
- [JsonExtensionData]
- public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
- }
-
- [Fact]
- public static void NestedClassWithObjectExtensionDataProperty()
- {
- var child = new ChildClassWithObject { Number = 2 };
- child.ExtensionData.Add("SpecialInformation", "I am child class");
-
- var parent = new ParentClassWithObject { Text = "Hello World" };
- parent.ExtensionData.Add("SpecialInformation", "I am parent class");
- parent.Child = child;
-
- // The extension data is based on the raw strings added above and not JsonElement.
- Assert.Equal("Hello World", parent.Text);
- Assert.IsType<string>(parent.ExtensionData["SpecialInformation"]);
- Assert.Equal("I am parent class", (string)parent.ExtensionData["SpecialInformation"]);
- Assert.Equal(2, parent.Child.Number);
- Assert.IsType<string>(parent.Child.ExtensionData["SpecialInformation"]);
- Assert.Equal("I am child class", (string)parent.Child.ExtensionData["SpecialInformation"]);
-
- // Round-trip and verify. Extension data is now based on JsonElement.
- string json = JsonSerializer.Serialize(parent);
- parent = JsonSerializer.Deserialize<ParentClassWithObject>(json);
-
- Assert.Equal("Hello World", parent.Text);
- Assert.IsType<JsonElement>(parent.ExtensionData["SpecialInformation"]);
- Assert.Equal("I am parent class", ((JsonElement)parent.ExtensionData["SpecialInformation"]).GetString());
- Assert.Equal(2, parent.Child.Number);
- Assert.IsType<JsonElement>(parent.Child.ExtensionData["SpecialInformation"]);
- Assert.Equal("I am child class", ((JsonElement)parent.Child.ExtensionData["SpecialInformation"]).GetString());
- }
-
- private class ParentClassWithJsonElement
- {
- public string Text { get; set; }
-
- public List<ChildClassWithJsonElement> Children { get; set; } = new List<ChildClassWithJsonElement>();
-
- [JsonExtensionData]
- // Use SortedDictionary as verification of supporting derived dictionaries.
- public SortedDictionary<string, JsonElement> ExtensionData { get; set; } = new SortedDictionary<string, JsonElement>();
- }
-
- private class ChildClassWithJsonElement
- {
- public int Number { get; set; }
-
- [JsonExtensionData]
- public Dictionary<string, JsonElement> ExtensionData { get; set; } = new Dictionary<string, JsonElement>();
- }
-
- [Fact]
- public static void NestedClassWithJsonElementExtensionDataProperty()
- {
- var child = new ChildClassWithJsonElement { Number = 4 };
- child.ExtensionData.Add("SpecialInformation", JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes("I am child class")).RootElement);
-
- var parent = new ParentClassWithJsonElement { Text = "Hello World" };
- parent.ExtensionData.Add("SpecialInformation", JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes("I am parent class")).RootElement);
- parent.Children.Add(child);
-
- Verify();
-
- // Round-trip and verify.
- string json = JsonSerializer.Serialize(parent);
- parent = JsonSerializer.Deserialize<ParentClassWithJsonElement>(json);
- Verify();
-
- void Verify()
- {
- Assert.Equal("Hello World", parent.Text);
- Assert.Equal("I am parent class", parent.ExtensionData["SpecialInformation"].GetString());
- Assert.Equal(1, parent.Children.Count);
- Assert.Equal(4, parent.Children[0].Number);
- Assert.Equal("I am child class", parent.Children[0].ExtensionData["SpecialInformation"].GetString());
- }
- }
-
- [Fact]
- public static void DeserializeIntoObjectProperty()
- {
- ClassWithExtensionPropertyAsObject obj;
- string json;
-
- // Baseline dictionary.
- json = @"{""MyDict"":{""Property1"":1}}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyDict"]).EnumerateObject().First().Value.GetInt32());
-
- // Attempt to deserialize directly into the overflow property; this is just added as a normal missing property like MyDict above.
- json = @"{""MyOverflow"":{""Property1"":1}}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyOverflow"]).EnumerateObject().First().Value.GetInt32());
-
- // Attempt to deserialize null into the overflow property. This is also treated as a missing property.
- json = @"{""MyOverflow"":null}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Null(obj.MyOverflow["MyOverflow"]);
-
- // Attempt to deserialize object into the overflow property. This is also treated as a missing property.
- json = @"{""MyOverflow"":{}}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(JsonValueKind.Object, ((JsonElement)obj.MyOverflow["MyOverflow"]).ValueKind);
- }
-
- [Fact]
- public static void DeserializeIntoMultipleDictionaries()
- {
- ClassWithMultipleDictionaries obj;
- string json;
-
- // Baseline dictionary.
- json = @"{""ActualDictionary"":{""Key"": {""Property0"":-1}},""MyDict"":{""Property1"":1}}";
- obj = JsonSerializer.Deserialize<ClassWithMultipleDictionaries>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(1, ((JsonElement)obj.MyOverflow["MyDict"]).EnumerateObject().First().Value.GetInt32());
- Assert.Equal(1, obj.ActualDictionary.Count);
- Assert.Equal(-1, ((JsonElement)obj.ActualDictionary["Key"]).EnumerateObject().First().Value.GetInt32());
-
- // Attempt to deserialize null into the dictionary and overflow property. This is also treated as a missing property.
- json = @"{""ActualDictionary"":null,""MyOverflow"":null}";
- obj = JsonSerializer.Deserialize<ClassWithMultipleDictionaries>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Null(obj.MyOverflow["MyOverflow"]);
- Assert.Null(obj.ActualDictionary);
-
- // Attempt to deserialize object into the dictionary and overflow property. This is also treated as a missing property.
- json = @"{""ActualDictionary"":{},""MyOverflow"":{}}";
- obj = JsonSerializer.Deserialize<ClassWithMultipleDictionaries>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(JsonValueKind.Object, ((JsonElement)obj.MyOverflow["MyOverflow"]).ValueKind);
- Assert.Equal(0, obj.ActualDictionary.Count);
- }
-
- [Fact]
- public static void DeserializeIntoJsonElementProperty()
- {
- ClassWithExtensionPropertyAsJsonElement obj;
- string json;
-
- // Baseline dictionary.
- json = @"{""MyDict"":{""Property1"":1}}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(1, obj.MyOverflow["MyDict"].EnumerateObject().First().Value.GetInt32());
-
- // Attempt to deserialize directly into the overflow property; this is just added as a normal missing property like MyDict above.
- json = @"{""MyOverflow"":{""Property1"":1}}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(1, obj.MyOverflow["MyOverflow"].EnumerateObject().First().Value.GetInt32());
-
- // Attempt to deserialize null into the overflow property. This is also treated as a missing property.
- json = @"{""MyOverflow"":null}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(JsonValueKind.Null, obj.MyOverflow["MyOverflow"].ValueKind);
-
- // Attempt to deserialize object into the overflow property. This is also treated as a missing property.
- json = @"{""MyOverflow"":{}}";
- obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(json);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(JsonValueKind.Object, obj.MyOverflow["MyOverflow"].ValueKind);
- }
-
- [Fact]
- public static void SerializerOutputRoundtripsWhenEscaping()
- {
- string jsonString = "{\"\u6C49\u5B57\":\"abc\",\"Class\":{\"\u6F22\u5B57\":\"xyz\"},\"\u62DC\u6258\":{\"\u62DC\u6258\u62DC\u6258\":1}}";
-
- ClassWithEscapedProperty input = JsonSerializer.Deserialize<ClassWithEscapedProperty>(jsonString);
-
- Assert.Equal("abc", input.\u6C49\u5B57);
- Assert.Equal("xyz", input.Class.\u6F22\u5B57);
-
- string normalizedString = JsonSerializer.Serialize(input);
-
- Assert.Equal(normalizedString, JsonSerializer.Serialize(JsonSerializer.Deserialize<ClassWithEscapedProperty>(normalizedString)));
- }
-
- public class ClassWithEscapedProperty
- {
- public string \u6C49\u5B57 { get; set; }
- public NestedClassWithEscapedProperty Class { get; set; }
-
- [JsonExtensionData]
- public Dictionary<string, object> Overflow { get; set; }
- }
-
- public class NestedClassWithEscapedProperty
- {
- public string \u6F22\u5B57 { get; set; }
- }
-
- private class ClassWithInvalidExtensionPropertyStringString
- {
- [JsonExtensionData]
- public Dictionary<string, string> MyOverflow { get; set; }
- }
-
- private class ClassWithInvalidExtensionPropertyObjectString
- {
- [JsonExtensionData]
- public Dictionary<DummyObj, string> MyOverflow { get; set; }
- }
-
- private class ClassWithInvalidExtensionPropertyStringJsonNode
- {
- [JsonExtensionData]
- public Dictionary<string, JsonNode> MyOverflow { get; set; }
- }
-
- [Fact]
- public static void ExtensionProperty_InvalidDictionary()
- {
- var obj1 = new ClassWithInvalidExtensionPropertyStringString();
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj1));
-
- var obj2 = new ClassWithInvalidExtensionPropertyObjectString();
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj2));
-
- var obj3 = new ClassWithInvalidExtensionPropertyStringJsonNode();
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj3));
- }
-
- private class ClassWithExtensionPropertyAlreadyInstantiated
- {
- public ClassWithExtensionPropertyAlreadyInstantiated()
- {
- MyOverflow = new Dictionary<string, object>();
- }
-
- [JsonExtensionData]
- public Dictionary<string, object> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyAsObject
- {
- [JsonExtensionData]
- public Dictionary<string, object> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyAsJsonElement
- {
- [JsonExtensionData]
- public Dictionary<string, JsonElement> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyAsJsonObject
- {
- [JsonExtensionData]
- public JsonObject MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyAsSystemObject
- {
- [JsonExtensionData]
- public object MyOverflow { get; set; }
- }
-
- private class ClassWithMultipleDictionaries
- {
- [JsonExtensionData]
- public Dictionary<string, object> MyOverflow { get; set; }
-
- public Dictionary<string, object> ActualDictionary { get; set; }
- }
-
- [Fact]
- public static void DeserializeIntoImmutableDictionaryProperty()
- {
- // baseline
- JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutable>(@"{}");
- JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutableJsonElement>(@"{}");
- JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructor>(@"{}");
- JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructorJsonElement>(@"{}");
-
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutable>("{\"hello\":\"world\"}"));
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutableJsonElement>("{\"hello\":\"world\"}"));
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructor>("{\"hello\":\"world\"}"));
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructorJsonElement>("{\"hello\":\"world\"}"));
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyCustomIImmutable>("{\"hello\":\"world\"}"));
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyCustomIImmutableJsonElement>("{\"hello\":\"world\"}"));
- }
-
- [Fact]
- public static void SerializeIntoImmutableDictionaryProperty()
- {
- // attempt to serialize a null immutable dictionary
- string expectedJson = "{}";
- var obj = new ClassWithExtensionPropertyAsImmutable();
- var json = JsonSerializer.Serialize(obj);
- Assert.Equal(expectedJson, json);
-
- // attempt to serialize an empty immutable dictionary
- expectedJson = "{}";
- obj = new ClassWithExtensionPropertyAsImmutable();
- obj.MyOverflow = ImmutableDictionary<string, object>.Empty;
- json = JsonSerializer.Serialize(obj);
- Assert.Equal(expectedJson, json);
-
- // attempt to serialize a populated immutable dictionary
- expectedJson = "{\"hello\":\"world\"}";
- obj = new ClassWithExtensionPropertyAsImmutable();
- var dictionaryStringObject = new Dictionary<string, object> { { "hello", "world" } };
- obj.MyOverflow = ImmutableDictionary.CreateRange(dictionaryStringObject);
- json = JsonSerializer.Serialize(obj);
- Assert.Equal(expectedJson, json);
- }
-
- private class ClassWithExtensionPropertyAsImmutable
- {
- [JsonExtensionData]
- public ImmutableDictionary<string, object> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyAsImmutableJsonElement
- {
- [JsonExtensionData]
- public ImmutableDictionary<string, JsonElement> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyPrivateConstructor
- {
- [JsonExtensionData]
- public GenericIDictionaryWrapperPrivateConstructor<string, object> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyPrivateConstructorJsonElement
- {
- [JsonExtensionData]
- public GenericIDictionaryWrapperPrivateConstructor<string, JsonElement> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyCustomIImmutable
- {
- [JsonExtensionData]
- public GenericIImmutableDictionaryWrapper<string, object> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyCustomIImmutableJsonElement
- {
- [JsonExtensionData]
- public GenericIImmutableDictionaryWrapper<string, JsonElement> MyOverflow { get; set; }
- }
-
- [Theory]
- [InlineData(typeof(ClassWithExtensionPropertyNoGenericParameters))]
- [InlineData(typeof(ClassWithExtensionPropertyOneGenericParameter))]
- [InlineData(typeof(ClassWithExtensionPropertyThreeGenericParameters))]
- public static void DeserializeIntoGenericDictionaryParameterCount(Type type)
- {
- object obj = JsonSerializer.Deserialize("{\"hello\":\"world\"}", type);
-
- IDictionary<string, object> extData = (IDictionary<string, object>)type.GetProperty("MyOverflow").GetValue(obj)!;
- Assert.Equal("world", ((JsonElement)extData["hello"]).GetString());
- }
-
- private class ClassWithExtensionPropertyNoGenericParameters
- {
- [JsonExtensionData]
- public StringToObjectIDictionaryWrapper MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyOneGenericParameter
- {
- [JsonExtensionData]
- public StringToGenericIDictionaryWrapper<object> MyOverflow { get; set; }
- }
-
- private class ClassWithExtensionPropertyThreeGenericParameters
- {
- [JsonExtensionData]
- public GenericIDictonaryWrapperThreeGenericParameters<string, object, string> MyOverflow { get; set; }
- }
-
- [Fact]
- public static void CustomObjectConverterInExtensionProperty()
- {
- const string Json = "{\"hello\": \"world\"}";
-
- var options = new JsonSerializerOptions();
- options.Converters.Add(new ObjectConverter());
-
- ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(Json, options);
- object overflowProp = obj.MyOverflow["hello"];
- Assert.IsType<string>(overflowProp);
- Assert.Equal("world!!!", ((string)overflowProp));
-
- string newJson = JsonSerializer.Serialize(obj, options);
- Assert.Equal("{\"hello\":\"world!!!\"}", newJson);
- }
-
- private class ObjectConverter : JsonConverter<object>
- {
- public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- return reader.GetString() + "!!!";
- }
-
- public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
- {
- // Since we are in a user-provided (not internal to S.T.Json) object converter,
- // this converter will be called, not the internal string converter.
- writer.WriteStringValue((string)value);
- }
- }
-
- [Fact]
- public static void CustomJsonElementConverterInExtensionProperty()
- {
- const string Json = "{\"hello\": \"world\"}";
-
- var options = new JsonSerializerOptions();
- options.Converters.Add(new JsonElementConverter());
-
- ClassWithExtensionPropertyAsJsonElement obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(Json, options);
- JsonElement overflowProp = obj.MyOverflow["hello"];
- Assert.Equal(JsonValueKind.Undefined, overflowProp.ValueKind);
-
- string newJson = JsonSerializer.Serialize(obj, options);
- Assert.Equal("{\"hello\":{\"Hi\":\"There\"}}", newJson);
- }
-
- private class JsonElementConverter : JsonConverter<JsonElement>
- {
- public override JsonElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- // Just return an empty JsonElement.
- reader.Skip();
- return new JsonElement();
- }
-
- public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
- {
- // Write a string we can test against easily.
- writer.WriteStartObject();
- writer.WriteString("Hi", "There");
- writer.WriteEndObject();
- }
- }
-
- [Fact]
- public static void CustomJsonObjectConverterInExtensionProperty()
- {
- const string Json = "{\"hello\": \"world\"}";
-
- var options = new JsonSerializerOptions();
- options.Converters.Add(new JsonObjectConverter());
-
- // A custom converter for JsonObject is not allowed on an extension property.
- InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() =>
- JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonObject>(Json, options));
-
- Assert.Contains("JsonObject", ex.ToString());
- }
-
- private class JsonObjectConverter : JsonConverter<JsonObject>
- {
- public override JsonObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- // Just return an empty JsonElement.
- reader.Skip();
- return new JsonObject();
- }
-
- public override void Write(Utf8JsonWriter writer, JsonObject value, JsonSerializerOptions options)
- {
- // Write a string we can test against easily.
- writer.WriteStartObject();
- writer.WriteString("Hi", "There");
- writer.WriteEndObject();
- }
- }
-
- [Fact]
- public static void EmptyPropertyAndExtensionData_PropertyFirst()
- {
- // Verify any caching treats real property (with empty name) differently than a missing property.
-
- ClassWithEmptyPropertyNameAndExtensionProperty obj;
-
- // Create a new options instances to re-set any caches.
- JsonSerializerOptions options = new JsonSerializerOptions();
-
- // First use an empty property.
- string json = @"{"""":43}";
- obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
- Assert.Equal(43, obj.MyInt1);
- Assert.Null(obj.MyOverflow);
-
- // Then populate cache with a missing property name.
- json = @"{""DoesNotExist"":42}";
- obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
- Assert.Equal(0, obj.MyInt1);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(42, obj.MyOverflow["DoesNotExist"].GetInt32());
- }
-
- [Fact]
- public static void EmptyPropertyNameAndExtensionData_ExtDataFirst()
- {
- // Verify any caching treats real property (with empty name) differently than a missing property.
-
- ClassWithEmptyPropertyNameAndExtensionProperty obj;
-
- // Create a new options instances to re-set any caches.
- JsonSerializerOptions options = new JsonSerializerOptions();
-
- // First populate cache with a missing property name.
- string json = @"{""DoesNotExist"":42}";
- obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
- Assert.Equal(0, obj.MyInt1);
- Assert.Equal(1, obj.MyOverflow.Count);
- Assert.Equal(42, obj.MyOverflow["DoesNotExist"].GetInt32());
-
- // Then use an empty property.
- json = @"{"""":43}";
- obj = JsonSerializer.Deserialize<ClassWithEmptyPropertyNameAndExtensionProperty>(json, options);
- Assert.Equal(43, obj.MyInt1);
- Assert.Null(obj.MyOverflow);
- }
-
- [Fact]
- public static void ExtensionDataDictionarySerialize_DoesNotHonor()
- {
- var options = new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
- };
-
- EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize<EmptyClassWithExtensionProperty>(@"{""Key1"": 1}", options);
-
- // Ignore naming policy for extension data properties by default.
- Assert.False(obj.MyOverflow.ContainsKey("key1"));
- Assert.Equal(1, obj.MyOverflow["Key1"].GetInt32());
- }
-
- [Theory]
- [InlineData(0x1, 'v')]
- [InlineData(0x1, '\u0467')]
- [InlineData(0x10, 'v')]
- [InlineData(0x10, '\u0467')]
- [InlineData(0x100, 'v')]
- [InlineData(0x100, '\u0467')]
- [InlineData(0x1000, 'v')]
- [InlineData(0x1000, '\u0467')]
- [InlineData(0x10000, 'v')]
- [InlineData(0x10000, '\u0467')]
- public static void LongPropertyNames(int propertyLength, char ch)
- {
- // Although the CLR may limit member length to 1023 bytes, the serializer doesn't have a hard limit.
-
- string val = new string(ch, propertyLength);
- string json = @"{""" + val + @""":1}";
-
- EmptyClassWithExtensionProperty obj = JsonSerializer.Deserialize<EmptyClassWithExtensionProperty>(json);
-
- Assert.True(obj.MyOverflow.ContainsKey(val));
-
- var options = new JsonSerializerOptions
- {
- // Avoid escaping '\u0467'.
- Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
- };
-
- string jsonRoundTripped = JsonSerializer.Serialize(obj, options);
- Assert.Equal(json, jsonRoundTripped);
- }
-
- public class EmptyClassWithExtensionProperty
- {
- [JsonExtensionData]
- public IDictionary<string, JsonElement> MyOverflow { get; set; }
- }
-
- public class ClassWithEmptyPropertyNameAndExtensionProperty
- {
- [JsonPropertyName("")]
- public int MyInt1 { get; set; }
-
- [JsonExtensionData]
- public IDictionary<string, JsonElement> MyOverflow { get; set; }
- }
+ public ExtensionDataTestsDynamic() : base(JsonSerializerWrapperForString.StringSerializer) { }
}
}
<Compile Include="..\Common\ConstructorTests\ConstructorTests.Exceptions.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.Exceptions.cs" />
<Compile Include="..\Common\ConstructorTests\ConstructorTests.ParameterMatching.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.ParameterMatching.cs" />
<Compile Include="..\Common\ConstructorTests\ConstructorTests.Stream.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ConstructorTests\ConstructorTests.Stream.cs" />
+ <Compile Include="..\Common\ExtensionDataTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\ExtensionDataTests.cs" />
<Compile Include="..\Common\JsonSerializerWrapperForStream.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapperForStream.cs" />
<Compile Include="..\Common\JsonSerializerWrapperForString.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapperForString.cs" />
<Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />