{
internal enum ClassType
{
+ /// <summary>
+ /// Types that are not supported yet by source gen including types with constructor parameters.
+ /// </summary>
TypeUnsupportedBySourceGen = 0,
Object = 1,
KnownType = 2,
- TypeWithDesignTimeProvidedCustomConverter = 3,
- Enumerable = 4,
- Dictionary = 5,
- Nullable = 6,
- Enum = 7
+ /// <summary>
+ /// Known types such as System.Type and System.IntPtr that throw NotSupportedException.
+ /// </summary>
+ KnownUnsupportedType = 3,
+ TypeWithDesignTimeProvidedCustomConverter = 4,
+ Enumerable = 5,
+ Dictionary = 6,
+ Nullable = 7,
+ Enum = 8
}
}
private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues";
private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo";
private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo";
+ private const string NotSupportedExceptionTypeRef = "global::System.NotSupportedException";
private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor(
id: "SYSLIB1030",
}
}
break;
+ case ClassType.KnownUnsupportedType:
+ {
+ source = GenerateForUnsupportedType(typeGenerationSpec);
+ }
+ break;
case ClassType.TypeUnsupportedBySourceGen:
{
_sourceProductionContext.ReportDiagnostic(
return GenerateForType(typeMetadata, metadataInitSource);
}
+ private string GenerateForUnsupportedType(TypeGenerationSpec typeMetadata)
+ {
+ string typeCompilableName = typeMetadata.TypeRef;
+ string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
+
+ string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeCompilableName}>());";
+
+ return GenerateForType(typeMetadata, metadataInitSource);
+ }
+
private string GenerateForEnum(TypeGenerationSpec typeMetadata)
{
string typeCompilableName = typeMetadata.TypeRef;
private const string JsonSerializerAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute";
private const string JsonSourceGenerationOptionsAttributeFullName = "System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute";
+ private const string DateOnlyFullName = "System.DateOnly";
+ private const string TimeOnlyFullName = "System.TimeOnly";
+ private const string IAsyncEnumerableFullName = "System.Collections.Generic.IAsyncEnumerable`1";
+
private readonly Compilation _compilation;
private readonly SourceProductionContext _sourceProductionContext;
private readonly MetadataLoadContextInternal _metadataLoadContext;
private readonly Type? _versionType;
private readonly Type? _jsonElementType;
+ // Unsupported types
+ private readonly Type _typeType;
+ private readonly Type _serializationInfoType;
+ private readonly Type _intPtrType;
+ private readonly Type _uIntPtrType;
+
+ // Unsupported types that may not resolve
+ private readonly Type? _iAsyncEnumerableGenericType;
+ private readonly Type? _dateOnlyType;
+ private readonly Type? _timeOnlyType;
+
private readonly HashSet<Type> _numberTypes = new();
private readonly HashSet<Type> _knownTypes = new();
+ private readonly HashSet<Type> _knownUnsupportedTypes = new();
/// <summary>
/// Type information for member types in input object graphs.
_versionType = _metadataLoadContext.Resolve(typeof(Version));
_jsonElementType = _metadataLoadContext.Resolve(JsonElementFullName);
+ // Unsupported types.
+ _typeType = _metadataLoadContext.Resolve(typeof(Type));
+ _serializationInfoType = _metadataLoadContext.Resolve(typeof(Runtime.Serialization.SerializationInfo));
+ _intPtrType = _metadataLoadContext.Resolve(typeof(IntPtr));
+ _uIntPtrType = _metadataLoadContext.Resolve(typeof(UIntPtr));
+ _iAsyncEnumerableGenericType = _metadataLoadContext.Resolve(IAsyncEnumerableFullName);
+ _dateOnlyType = _metadataLoadContext.Resolve(DateOnlyFullName);
+ _timeOnlyType = _metadataLoadContext.Resolve(TimeOnlyFullName);
+
PopulateKnownTypes();
}
collectionValueType = _objectType;
}
}
+ else if (_knownUnsupportedTypes.Contains(type) ||
+ ImplementsIAsyncEnumerableInterface(type))
+ {
+ classType = ClassType.KnownUnsupportedType;
+ }
else
{
bool useDefaultCtorInAnnotatedStructs = !type.IsKeyValuePair(_keyValuePair);
return typeMetadata;
}
+ private bool ImplementsIAsyncEnumerableInterface(Type type)
+ {
+ if (_iAsyncEnumerableGenericType == null)
+ {
+ return false;
+ }
+
+ return type.GetCompatibleGenericInterface(_iAsyncEnumerableGenericType) is not null;
+ }
+
private Type GetCompatibleGenericBaseClass(Type type, Type baseType)
=> type.GetCompatibleGenericBaseClass(baseType, _objectType);
private void PopulateKnownTypes()
{
PopulateNumberTypes();
+
Debug.Assert(_knownTypes != null);
Debug.Assert(_numberTypes != null);
+ Debug.Assert(_knownUnsupportedTypes != null);
_knownTypes.UnionWith(_numberTypes);
_knownTypes.Add(_booleanType);
_knownTypes.Add(_uriType);
_knownTypes.Add(_versionType);
_knownTypes.Add(_jsonElementType);
+
+ _knownUnsupportedTypes.Add(_typeType);
+ _knownUnsupportedTypes.Add(_serializationInfoType);
+ _knownUnsupportedTypes.Add(_intPtrType);
+ _knownUnsupportedTypes.Add(_uIntPtrType);
+
+ if (_dateOnlyType != null)
+ {
+ _knownUnsupportedTypes.Add(_dateOnlyType);
+ }
+
+ if (_timeOnlyType != null)
+ {
+ _knownUnsupportedTypes.Add(_timeOnlyType);
+ }
}
}
}
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; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateValueInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; }
+ public static System.Text.Json.Serialization.JsonConverter<T> GetUnsupportedTypeConverter<T>() { throw null; }
public static System.Text.Json.Serialization.JsonConverter<T> GetEnumConverter<T>(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; }
public static System.Text.Json.Serialization.JsonConverter<T?> GetNullableConverter<T>(System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> underlyingTypeInfo) where T : struct { throw null; }
}
<value>The type '{0}' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types.</value>
</data>
<data name="SerializeTypeInstanceNotSupported" xml:space="preserve">
- <value>Serialization and deserialization of '{0}' instances are not supported and should be avoided since they can lead to security issues.</value>
+ <value>Serialization and deserialization of '{0}' instances are not supported.</value>
</data>
<data name="JsonIncludeOnNonPublicInvalid" xml:space="preserve">
<value>The non-public property '{0}' on type '{1}' is annotated with 'JsonIncludeAttribute' which is invalid.</value>
<Compile Include="System\Text\Json\Serialization\Converters\Value\DateTimeConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\DateTimeOffsetConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\DecimalConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\Value\DisallowedTypeConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\Value\DisallowedTypeConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\DoubleConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\EnumConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\EnumConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt16Converter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt32Converter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt64Converter.cs" />
+ <Compile Include="System\Text\Json\Serialization\Converters\Value\UnsupportedTypeConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\Converters\Value\UnsupportedTypeConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\UriConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\VersionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\IgnoreReferenceHandler.cs" />
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class DisallowedTypeConverter<T> : JsonConverter<T>
+ internal sealed class UnsupportedTypeConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
throw new NotSupportedException(SR.Format(SR.SerializeTypeInstanceNotSupported, typeof(T).FullName));
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class DisallowedTypeConverterFactory : JsonConverterFactory
+ internal sealed class UnsupportedTypeConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type type)
{
- // If a value type is added, also add a test that
- // shows NSE is thrown when Nullable<T> is (de)serialized.
+ // If a type is added, also add to the SourceGeneration project.
return
// There's no safe way to construct a Type from untrusted user input.
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
{
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
- typeof(DisallowedTypeConverter<>).MakeGenericType(type),
+ typeof(UnsupportedTypeConverter<>).MakeGenericType(type),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: null,
s_defaultFactoryConverters = new JsonConverter[]
{
// Check for disallowed types.
- new DisallowedTypeConverterFactory(),
+ new UnsupportedTypeConverterFactory(),
// Nullable converter should always be next since it forwards to any nullable type.
new NullableConverterFactory(),
new EnumConverterFactory(),
private static JsonConverter<Version>? s_versionConverter;
/// <summary>
+ /// Creates a <see cref="JsonConverter{T}"/> instance that throws <see cref="NotSupportedException"/>.
+ /// </summary>
+ /// <typeparam name="T">The generic definition for the type.</typeparam>
+ /// <returns></returns>
+ public static JsonConverter<T> GetUnsupportedTypeConverter<T>()
+ => new UnsupportedTypeConverter<T>();
+
+ /// <summary>
/// Creates a <see cref="JsonConverter{T}"/> instance that converts <typeparamref name="T"/> values.
/// </summary>
/// <typeparam name="T">The generic definition for the enum type.</typeparam>
static void AppendStackFrame(StringBuilder sb, ref WriteStackFrame frame)
{
// Append the property name.
- string? propertyName = frame.DeclaredJsonPropertyInfo?.MemberInfo?.Name;
+ string? propertyName = frame.DeclaredJsonPropertyInfo?.ClrName;
if (propertyName == null)
{
// Attempt to get the JSON property name from the property name specified in re-entry.
--- /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.Runtime.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+ public abstract class UnsupportedTypesTests : SerializerTests
+ {
+ private bool SupportsJsonPathOnSerialize { get; init; }
+
+ public UnsupportedTypesTests(
+ JsonSerializerWrapperForString serializerWrapper,
+ bool supportsJsonPathOnSerialize) : base(serializerWrapper)
+ {
+ SupportsJsonPathOnSerialize = supportsJsonPathOnSerialize;
+ }
+
+ [Fact]
+ public async Task DeserializeUnsupportedType()
+ {
+ // Any test payload is fine.
+ string json = @"""Some string""";
+
+ await RunTest<Type>(json);
+ await RunTest<SerializationInfo>(json);
+ await RunTest<IntPtr>(json);
+ await RunTest<IntPtr?>(json); // One nullable variation.
+ await RunTest<UIntPtr>(json);
+#if NETCOREAPP
+ await RunTest<DateOnly>(json);
+ await RunTest<TimeOnly>(json);
+#endif
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ await RunTest<IAsyncEnumerable<int>>(json);
+ await RunTest<ClassThatImplementsIAsyncEnumerable>(json);
+#endif
+
+ async Task RunTest<T>(string json)
+ {
+ Type type = GetNullableOfTUnderlyingType(typeof(T), out bool isNullableOfT);
+ string fullName = type.FullName;
+
+ NotSupportedException ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<T>(json));
+ string exAsStr = ex.ToString();
+ Assert.Contains(fullName, exAsStr);
+ Assert.Contains("$", exAsStr);
+
+ json = $@"{{""Prop"":{json}}}";
+
+ ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithType<T>>(json));
+ exAsStr = ex.ToString();
+ Assert.Contains(fullName, exAsStr);
+ Assert.Contains("$.Prop", exAsStr);
+
+ // Verify Nullable<> semantics. NSE is not thrown because the serializer handles null.
+ if (isNullableOfT)
+ {
+ Assert.Null(JsonSerializer.Deserialize<T>("null"));
+
+ json = $@"{{""Prop"":null}}";
+ ClassWithType<T> obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithType<T>>(json);
+ Assert.Null(obj.Prop);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task SerializeUnsupportedType()
+ {
+ await RunTest(typeof(int));
+ await RunTest(new SerializationInfo(typeof(Type), new FormatterConverter()));
+ await RunTest((IntPtr)123);
+ await RunTest<IntPtr?>(new IntPtr(123)); // One nullable variation.
+ await RunTest((UIntPtr)123);
+#if NETCOREAPP
+ await RunTest(DateOnly.MaxValue);
+ await RunTest(TimeOnly.MinValue);
+#endif
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ await RunTest(new ClassThatImplementsIAsyncEnumerable());
+#endif
+
+ async Task RunTest<T>(T value)
+ {
+ Type type = GetNullableOfTUnderlyingType(typeof(T), out bool isNullableOfT);
+ string fullName = type.FullName;
+
+ NotSupportedException ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(value));
+ string exAsStr = ex.ToString();
+ Assert.Contains(fullName, exAsStr);
+ Assert.Contains("$", exAsStr);
+
+ ClassWithType<T> obj = new ClassWithType<T> { Prop = value };
+
+ ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj));
+ exAsStr = ex.ToString();
+ Assert.Contains(fullName, exAsStr);
+
+ if (SupportsJsonPathOnSerialize)
+ {
+ Assert.Contains("$.Prop", exAsStr);
+ }
+ else
+ {
+ Assert.Contains("$.", exAsStr);
+ Assert.DoesNotContain("$.Prop", exAsStr);
+ }
+
+ // Verify null semantics. NSE is not thrown because the serializer handles null.
+ if (!type.IsValueType || isNullableOfT)
+ {
+ string serialized = await JsonSerializerWrapperForString.SerializeWrapper<T>((T)(object)null);
+ Assert.Equal("null", serialized);
+
+ obj.Prop = (T)(object)null;
+ serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal(@"{""Prop"":null}", serialized);
+
+ serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, new JsonSerializerOptions { IgnoreNullValues = true });
+ Assert.Equal(@"{}", serialized);
+ }
+ }
+ }
+
+ public class ClassWithIntPtr
+ {
+ public IntPtr MyIntPtr { get; set; }
+ }
+
+ public class ClassWithIntPtrConverter
+ {
+ [JsonConverter(typeof(IntPtrConverter))]
+ public IntPtr MyIntPtr { get; set; }
+ }
+
+ public class IntPtrConverter : JsonConverter<IntPtr>
+ {
+ public override IntPtr Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ int value = reader.GetInt32();
+ return new IntPtr(value);
+ }
+
+ public override void Write(Utf8JsonWriter writer, IntPtr value, JsonSerializerOptions options)
+ {
+ writer.WriteNumberValue(value.ToInt32());
+ }
+ }
+
+ [Fact]
+ public async Task RuntimeConverterIsSupported_IntPtr()
+ {
+ const string Json = "{\"MyIntPtr\":42}";
+ string serialized;
+ JsonSerializerOptions options = new();
+ options.Converters.Add(new IntPtrConverter());
+
+ serialized = await JsonSerializerWrapperForString.SerializeWrapper(new IntPtr(42), options);
+ Assert.Equal("42", serialized);
+
+ IntPtr intPtr = await JsonSerializerWrapperForString.DeserializeWrapper<IntPtr>("42", options);
+ Assert.Equal(42, intPtr.ToInt32());
+
+ ClassWithIntPtr obj = new() { MyIntPtr = new IntPtr(42) };
+ serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+ Assert.Equal(Json, serialized);
+
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithIntPtr>(Json, options);
+ Assert.Equal(42, obj.MyIntPtr.ToInt32());
+ }
+
+ [Fact]
+ public async Task CompileTimeConverterIsSupported_IntPtr()
+ {
+ const string Json = "{\"MyIntPtr\":42}";
+
+ ClassWithIntPtrConverter obj = new() { MyIntPtr = new IntPtr(42) };
+ string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal(Json, serialized);
+
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithIntPtrConverter>(Json);
+ Assert.Equal(42, obj.MyIntPtr.ToInt32());
+ }
+
+ public class ClassWithAsyncEnumerableConverter
+ {
+ [JsonConverter(typeof(AsyncEnumerableConverter))]
+ public ClassThatImplementsIAsyncEnumerable MyAsyncEnumerable { get; set; }
+ }
+
+ public class ClassThatImplementsIAsyncEnumerable : IAsyncEnumerable<int>
+ {
+ public string Status { get; set; } = "Created";
+
+ // Should not be called.
+ IAsyncEnumerator<int> IAsyncEnumerable<int>.GetAsyncEnumerator(CancellationToken cancellationToken) => throw new NotImplementedException();
+ }
+
+ public class AsyncEnumerableConverter : JsonConverter<ClassThatImplementsIAsyncEnumerable>
+ {
+ public override ClassThatImplementsIAsyncEnumerable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ Assert.Equal(JsonTokenType.StartArray, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.EndArray, reader.TokenType);
+ return new ClassThatImplementsIAsyncEnumerable { Status = "Read" };
+ }
+
+ public override void Write(Utf8JsonWriter writer, ClassThatImplementsIAsyncEnumerable value, JsonSerializerOptions options)
+ {
+ writer.WriteStartArray();
+ writer.WriteEndArray();
+ value.Status = "Write";
+ }
+ }
+
+ [Fact]
+ public async Task RuntimeConverterIsSupported_AsyncEnumerable()
+ {
+ const string Json = "{\"MyAsyncEnumerable\":[]}";
+
+ string serialized;
+ JsonSerializerOptions options = new();
+ options.Converters.Add(new AsyncEnumerableConverter());
+
+ ClassThatImplementsIAsyncEnumerable obj = new();
+ Assert.Equal("Created", obj.Status);
+ serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+ Assert.Equal("[]", serialized);
+ Assert.Equal("Write", obj.Status);
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassThatImplementsIAsyncEnumerable>("[]", options);
+ Assert.Equal("Read", obj.Status);
+
+ ClassWithAsyncEnumerableConverter poco = new();
+ poco.MyAsyncEnumerable = new();
+ Assert.Equal("Created", poco.MyAsyncEnumerable.Status);
+ serialized = await JsonSerializerWrapperForString.SerializeWrapper(poco, options);
+ Assert.Equal(Json, serialized);
+ Assert.Equal("Write", poco.MyAsyncEnumerable.Status);
+ poco = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithAsyncEnumerableConverter>(Json, options);
+ Assert.Equal("Read", poco.MyAsyncEnumerable.Status);
+ }
+
+ [Fact]
+ public async Task CompileTimeConverterIsSupported_AsyncEnumerable()
+ {
+ const string Json = "{\"MyAsyncEnumerable\":[]}";
+
+ ClassWithAsyncEnumerableConverter obj = new();
+ obj.MyAsyncEnumerable = new();
+ Assert.Equal("Created", obj.MyAsyncEnumerable.Status);
+
+ string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ Assert.Equal(Json, serialized);
+ Assert.Equal("Write", obj.MyAsyncEnumerable.Status);
+
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithAsyncEnumerableConverter>(Json);
+ Assert.Equal("Read", obj.MyAsyncEnumerable.Status);
+ }
+
+ public static Type GetNullableOfTUnderlyingType(Type type, out bool isNullableOfT)
+ {
+ isNullableOfT = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ return isNullableOfT ? type.GetGenericArguments()[0] : type;
+ }
+ }
+}
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
+using System.Text.Json.Serialization.Tests;
using Xunit;
namespace System.Text.Json.SourceGeneration.Tests
[Fact]
public virtual void RoundTripWithCustomPropertyConverterFactory_Class()
{
- const string Json = "{\"MyEnum\":\"A\"}";
+ const string Json = "{\"MyEnum\":\"One\"}";
ClassWithCustomConverterPropertyFactory obj = new()
{
- MyEnum = SampleEnum.A
+ MyEnum = SampleEnum.One
};
if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
else
{
obj = JsonSerializer.Deserialize(Json, DefaultContext.ClassWithCustomConverterPropertyFactory);
- Assert.Equal(SampleEnum.A, obj.MyEnum);
+ Assert.Equal(SampleEnum.One, obj.MyEnum);
}
}
[Fact]
public virtual void RoundTripWithCustomPropertyConverterFactory_Struct()
{
- const string Json = "{\"MyEnum\":\"A\"}";
+ const string Json = "{\"MyEnum\":\"One\"}";
StructWithCustomConverterPropertyFactory obj = new()
{
- MyEnum = SampleEnum.A
+ MyEnum = SampleEnum.One
};
if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
else
{
obj = JsonSerializer.Deserialize(Json, DefaultContext.StructWithCustomConverterPropertyFactory);
- Assert.Equal(SampleEnum.A, obj.MyEnum);
+ Assert.Equal(SampleEnum.One, obj.MyEnum);
}
}
--- /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.Runtime.Serialization;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Tests;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+ public sealed partial class UnsupportedTypesTests_Metadata : UnsupportedTypesTests
+ {
+ public UnsupportedTypesTests_Metadata() : base(
+ new StringSerializerWrapper(
+ UnsupportedTypesTestsContext_Metadata.Default,
+ (options) => new UnsupportedTypesTestsContext_Metadata(options)),
+ supportsJsonPathOnSerialize: true)
+ {
+ }
+
+ [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
+ // Supported types:
+ [JsonSerializable(typeof(int))]
+ [JsonSerializable(typeof(ClassWithIntPtrConverter))]
+ // Unsupported types:
+ [JsonSerializable(typeof(Type))]
+ [JsonSerializable(typeof(ClassWithType<Type>))]
+ [JsonSerializable(typeof(SerializationInfo))]
+ [JsonSerializable(typeof(ClassWithType<SerializationInfo>))]
+ [JsonSerializable(typeof(IntPtr))]
+ [JsonSerializable(typeof(ClassWithType<IntPtr>))]
+ [JsonSerializable(typeof(ClassWithIntPtr))]
+ [JsonSerializable(typeof(IntPtr?))]
+ [JsonSerializable(typeof(ClassWithType<IntPtr?>))]
+ [JsonSerializable(typeof(UIntPtr))]
+ [JsonSerializable(typeof(ClassWithType<UIntPtr>))]
+ [JsonSerializable(typeof(IAsyncEnumerable<int>))]
+ [JsonSerializable(typeof(ClassWithType<IAsyncEnumerable<int>>))]
+ [JsonSerializable(typeof(ClassThatImplementsIAsyncEnumerable))]
+ [JsonSerializable(typeof(ClassWithType<ClassThatImplementsIAsyncEnumerable>))]
+ [JsonSerializable(typeof(ClassWithAsyncEnumerableConverter))]
+
+#if NETCOREAPP
+ [JsonSerializable(typeof(DateOnly))]
+ [JsonSerializable(typeof(ClassWithType<DateOnly>))]
+ [JsonSerializable(typeof(TimeOnly))]
+ [JsonSerializable(typeof(ClassWithType<TimeOnly>))]
+#endif
+ internal sealed partial class UnsupportedTypesTestsContext_Metadata : JsonSerializerContext
+ {
+ }
+ }
+
+ public sealed partial class UnsupportedTypesTests_Default : UnsupportedTypesTests
+ {
+ public UnsupportedTypesTests_Default() : base(
+ new StringSerializerWrapper(
+ UnsupportedTypesTestsContext_Default.Default,
+ (options) => new UnsupportedTypesTestsContext_Default(options)),
+ supportsJsonPathOnSerialize: false)
+ {
+ }
+
+ // Supported types:
+ [JsonSerializable(typeof(int))]
+ [JsonSerializable(typeof(ClassWithIntPtrConverter))]
+ // Unsupported types:
+ [JsonSerializable(typeof(Type))]
+ [JsonSerializable(typeof(ClassWithType<Type>))]
+ [JsonSerializable(typeof(SerializationInfo))]
+ [JsonSerializable(typeof(ClassWithType<SerializationInfo>))]
+ [JsonSerializable(typeof(IntPtr))]
+ [JsonSerializable(typeof(ClassWithType<IntPtr>))]
+ [JsonSerializable(typeof(ClassWithIntPtr))]
+ [JsonSerializable(typeof(IntPtr?))]
+ [JsonSerializable(typeof(ClassWithType<IntPtr?>))]
+ [JsonSerializable(typeof(UIntPtr))]
+ [JsonSerializable(typeof(ClassWithType<UIntPtr>))]
+ [JsonSerializable(typeof(IAsyncEnumerable<int>))]
+ [JsonSerializable(typeof(ClassWithType<IAsyncEnumerable<int>>))]
+ [JsonSerializable(typeof(ClassThatImplementsIAsyncEnumerable))]
+ [JsonSerializable(typeof(ClassWithType<ClassThatImplementsIAsyncEnumerable>))]
+ [JsonSerializable(typeof(ClassWithAsyncEnumerableConverter))]
+#if NETCOREAPP
+ [JsonSerializable(typeof(DateOnly))]
+ [JsonSerializable(typeof(ClassWithType<DateOnly>))]
+ [JsonSerializable(typeof(TimeOnly))]
+ [JsonSerializable(typeof(ClassWithType<TimeOnly>))]
+#endif
+ internal sealed partial class UnsupportedTypesTestsContext_Default : JsonSerializerContext
+ {
+ }
+ }
+}
<Compile Include="..\Common\TestClasses\TestClasses.SimpleTestStruct.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.SimpleTestStruct.cs" />
<Compile Include="..\Common\TestClasses\TestClasses.SimpleTestStructWithFields.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.SimpleTestStructWithFields.cs" />
<Compile Include="..\Common\TestClasses\TestClasses.ValueTypedMember.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.ValueTypedMember.cs" />
+ <Compile Include="..\Common\UnsupportedTypesTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\UnsupportedTypesTests.cs" />
<Compile Include="ContextClasses.cs" />
<Compile Include="JsonSerializerContextTests.cs" />
<Compile Include="Serialization\CollectionTests.cs" />
<Compile Include="SerializationLogicTests.cs" />
<Compile Include="Serialization\PropertyNameTests.cs" />
<Compile Include="Serialization\PropertyVisibilityTests.cs" />
+ <Compile Include="Serialization\UnsupportedTypesTests.cs" />
<Compile Include="TestClasses.cs" />
<Compile Include="TestClasses.CustomConverters.cs" />
</ItemGroup>
public struct ClassWithCustomConverterPropertyFactory
{
[JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory
- public SampleEnum MyEnum { get; set; }
+ public Serialization.Tests.SampleEnum MyEnum { get; set; }
}
public struct StructWithCustomConverterPropertyFactory
{
[JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory
- public SampleEnum MyEnum { get; set; }
- }
-
- public enum SampleEnum
- {
- A = 1,
- B = 2
+ public Serialization.Tests.SampleEnum MyEnum { get; set; }
}
[JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid
Assert.DoesNotContain("Path: $", ex.Message);
}
- [Fact]
- public static void DeserializeUnsupportedType()
- {
- // Any test payload is fine.
- string json = @"""Some string""";
-
- RunTest<Type>(json);
- RunTest<SerializationInfo>(json);
- RunTest<IntPtr>(json);
- RunTest<UIntPtr>(json);
-#if NETCOREAPP
- RunTest<DateOnly>(json);
- RunTest<TimeOnly>(json);
-#endif
-
- void RunTest<T>(string json)
- {
- string fullName = typeof(T).FullName;
-
- NotSupportedException ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<T>(json));
- string exAsStr = ex.ToString();
- Assert.Contains(fullName, exAsStr);
- Assert.Contains("$", exAsStr);
-
- json = $@"{{""Prop"":{json}}}";
-
- ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithType<T>>(json));
- exAsStr = ex.ToString();
- Assert.Contains(fullName, exAsStr);
- Assert.Contains("$.Prop", exAsStr);
-
- // NSE is not thrown because the serializer handles null.
- if (!typeof(T).IsValueType)
- {
- Assert.Null(JsonSerializer.Deserialize<T>("null"));
-
- ClassWithType<T> obj = JsonSerializer.Deserialize<ClassWithType<T>>(@"{""Prop"":null}");
- Assert.Null(obj.Prop);
- }
- }
- }
-
- [Fact]
- public static void SerializeUnsupportedType()
- {
- RunTest(typeof(int));
- RunTest(new SerializationInfo(typeof(Type), new FormatterConverter()));
- RunTest((IntPtr)123);
- RunTest((UIntPtr)123);
-#if NETCOREAPP
- RunTest(DateOnly.MaxValue);
- RunTest(TimeOnly.MinValue);
-#endif
-
- void RunTest<T>(T value)
- {
- Type type = typeof(T);
- string fullName = type.FullName;
-
- NotSupportedException ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(value));
- string exAsStr = ex.ToString();
- Assert.Contains(fullName, exAsStr);
- Assert.Contains("$", exAsStr);
-
- ClassWithType<T> obj = new ClassWithType<T> { Prop = value };
-
- ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(obj));
- exAsStr = ex.ToString();
- Assert.Contains(fullName, exAsStr);
- Assert.Contains("$.Prop", exAsStr);
-
- if (!type.IsValueType)
- {
- string serialized = JsonSerializer.Serialize((T)(object)null);
- Assert.Equal("null", serialized);
-
- obj.Prop = (T)(object)null;
- serialized = JsonSerializer.Serialize(obj);
- Assert.Equal(@"{""Prop"":null}", serialized);
-
- serialized = JsonSerializer.Serialize(obj, new JsonSerializerOptions { IgnoreNullValues = true });
- Assert.Equal(@"{}", serialized);
- }
- }
- }
-
[Theory]
[InlineData(typeof(ClassWithBadCtor))]
[InlineData(typeof(StructWithBadCtor))]
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json.Serialization.Tests
+{
+ public sealed partial class UnsupportedTypesTestsDynamic : UnsupportedTypesTests
+ {
+ public UnsupportedTypesTestsDynamic() : base(
+ JsonSerializerWrapperForString.StringSerializer,
+ supportsJsonPathOnSerialize: true)
+ {
+ }
+ }
+}
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);net461</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Compile Include="..\Common\TestClasses\TestClasses.SimpleTestStruct.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.SimpleTestStruct.cs" />
<Compile Include="..\Common\TestClasses\TestClasses.SimpleTestStructWithFields.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.SimpleTestStructWithFields.cs" />
<Compile Include="..\Common\TestClasses\TestClasses.ValueTypedMember.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.ValueTypedMember.cs" />
+ <Compile Include="..\Common\UnsupportedTypesTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\UnsupportedTypesTests.cs" />
<Compile Include="BitStackTests.cs" />
<Compile Include="BufferFactory.cs" />
<Compile Include="BufferSegment.cs" />
<Compile Include="Serialization\Stream.ReadTests.cs" />
<Compile Include="Serialization\Stream.WriteTests.cs" />
<Compile Include="Serialization\TestData.cs" />
+ <Compile Include="Serialization\UnsupportedTypesTests.cs" />
<Compile Include="Serialization\Value.ReadTests.cs" />
<Compile Include="Serialization\Value.WriteTests.cs" />
<Compile Include="Serialization\WriteValueTests.cs" />