Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default null value on nullable types caused errors #2941

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ public NewtonsoftDataContractResolver(JsonSerializerSettings serializerSettings)

public DataContract GetDataContractForType(Type type)
{
if (type.IsOneOf(typeof(object), typeof(JToken), typeof(JObject), typeof(JArray)))
var effectiveType = Nullable.GetUnderlyingType(type) ?? type;
if (effectiveType.IsOneOf(typeof(object), typeof(JToken), typeof(JObject), typeof(JArray)))
{
return DataContract.ForDynamic(
underlyingType: type,
underlyingType: effectiveType,
jsonConverter: JsonConverterFunc);
}

var jsonContract = _contractResolver.ResolveContract(type);
var jsonContract = _contractResolver.ResolveContract(effectiveType);

if (jsonContract is JsonPrimitiveContract && !jsonContract.UnderlyingType.IsEnum)
{
Expand Down Expand Up @@ -134,7 +135,7 @@ public DataContract GetDataContractForType(Type type)
}

return DataContract.ForDynamic(
underlyingType: type,
underlyingType: effectiveType,
jsonConverter: JsonConverterFunc);
}

Expand Down Expand Up @@ -199,26 +200,26 @@ private List<DataProperty> GetDataPropertiesFor(JsonObjectContract jsonObjectCon

private static readonly Dictionary<Type, Tuple<DataType, string>> PrimitiveTypesAndFormats = new()
{
[ typeof(bool) ] = Tuple.Create(DataType.Boolean, (string)null),
[ typeof(byte) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(sbyte) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(short) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(ushort) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(int) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(uint) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(long) ] = Tuple.Create(DataType.Integer, "int64"),
[ typeof(ulong) ] = Tuple.Create(DataType.Integer, "int64"),
[ typeof(float) ] = Tuple.Create(DataType.Number, "float"),
[ typeof(double) ] = Tuple.Create(DataType.Number, "double"),
[ typeof(decimal) ] = Tuple.Create(DataType.Number, "double"),
[ typeof(byte[]) ] = Tuple.Create(DataType.String, "byte"),
[ typeof(string) ] = Tuple.Create(DataType.String, (string)null),
[ typeof(char) ] = Tuple.Create(DataType.String, (string)null),
[ typeof(DateTime) ] = Tuple.Create(DataType.String, "date-time"),
[ typeof(DateTimeOffset) ] = Tuple.Create(DataType.String, "date-time"),
[ typeof(Guid) ] = Tuple.Create(DataType.String, "uuid"),
[ typeof(Uri) ] = Tuple.Create(DataType.String, "uri"),
[ typeof(TimeSpan) ] = Tuple.Create(DataType.String, "date-span"),
[typeof(bool)] = Tuple.Create(DataType.Boolean, (string)null),
[typeof(byte)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(sbyte)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(short)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(ushort)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(int)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(uint)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(long)] = Tuple.Create(DataType.Integer, "int64"),
[typeof(ulong)] = Tuple.Create(DataType.Integer, "int64"),
[typeof(float)] = Tuple.Create(DataType.Number, "float"),
[typeof(double)] = Tuple.Create(DataType.Number, "double"),
[typeof(decimal)] = Tuple.Create(DataType.Number, "double"),
[typeof(byte[])] = Tuple.Create(DataType.String, "byte"),
[typeof(string)] = Tuple.Create(DataType.String, (string)null),
[typeof(char)] = Tuple.Create(DataType.String, (string)null),
[typeof(DateTime)] = Tuple.Create(DataType.String, "date-time"),
[typeof(DateTimeOffset)] = Tuple.Create(DataType.String, "date-time"),
[typeof(Guid)] = Tuple.Create(DataType.String, "uuid"),
[typeof(Uri)] = Tuple.Create(DataType.String, "uri"),
[typeof(TimeSpan)] = Tuple.Create(DataType.String, "date-span"),
#if NET6_0_OR_GREATER
[ typeof(DateOnly) ] = Tuple.Create(DataType.String, "date"),
[ typeof(TimeOnly) ] = Tuple.Create(DataType.String, "time"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,26 @@ public JsonSerializerDataContractResolver(JsonSerializerOptions serializerOption

public DataContract GetDataContractForType(Type type)
{
if (type.IsOneOf(typeof(object), typeof(JsonDocument), typeof(JsonElement)))
var effectiveType = Nullable.GetUnderlyingType(type) ?? type;
if (effectiveType.IsOneOf(typeof(object), typeof(JsonDocument), typeof(JsonElement)))
{
return DataContract.ForDynamic(
underlyingType: type,
jsonConverter: (value) => JsonConverterFunc(value, type));
underlyingType: effectiveType,
jsonConverter: (value) => JsonConverterFunc(value, effectiveType));
}

if (PrimitiveTypesAndFormats.TryGetValue(type, out var primitiveTypeAndFormat))
if (PrimitiveTypesAndFormats.TryGetValue(effectiveType, out var primitiveTypeAndFormat))
{
return DataContract.ForPrimitive(
underlyingType: type,
underlyingType: effectiveType,
dataType: primitiveTypeAndFormat.Item1,
dataFormat: primitiveTypeAndFormat.Item2,
jsonConverter: (value) => JsonConverterFunc(value, type));
}

if (type.IsEnum)
if (effectiveType.IsEnum)
{
var enumValues = type.GetEnumValues();
var enumValues = effectiveType.GetEnumValues();

// Test to determine if the serializer will treat as string
var serializeAsString =
Expand All @@ -51,18 +52,18 @@ public DataContract GetDataContractForType(Type type)

var exampleType = serializeAsString ?
typeof(string) :
type.GetEnumUnderlyingType();
effectiveType.GetEnumUnderlyingType();

primitiveTypeAndFormat = PrimitiveTypesAndFormats[exampleType];

return DataContract.ForPrimitive(
underlyingType: type,
underlyingType: effectiveType,
dataType: primitiveTypeAndFormat.Item1,
dataFormat: primitiveTypeAndFormat.Item2,
jsonConverter: (value) => JsonConverterFunc(value, type));
}

if (IsSupportedDictionary(type, out Type keyType, out Type valueType))
if (IsSupportedDictionary(effectiveType, out Type keyType, out Type valueType))
{
IEnumerable<string> keys = null;

Expand All @@ -79,25 +80,25 @@ public DataContract GetDataContractForType(Type type)
}

return DataContract.ForDictionary(
underlyingType: type,
underlyingType: effectiveType,
valueType: valueType,
keys: keys,
jsonConverter: (value) => JsonConverterFunc(value, type));
jsonConverter: (value) => JsonConverterFunc(value, effectiveType));
}

if (IsSupportedCollection(type, out Type itemType))
if (IsSupportedCollection(effectiveType, out Type itemType))
{
return DataContract.ForArray(
underlyingType: type,
underlyingType: effectiveType,
itemType: itemType,
jsonConverter: (value) => JsonConverterFunc(value, type));
jsonConverter: (value) => JsonConverterFunc(value, effectiveType));
}

return DataContract.ForObject(
underlyingType: type,
underlyingType: effectiveType,
properties: GetDataPropertiesFor(type, out Type extensionDataType),
extensionDataType: extensionDataType,
jsonConverter: (value) => JsonConverterFunc(value, type));
jsonConverter: (value) => JsonConverterFunc(value, effectiveType));
}

private string JsonConverterFunc(object value, Type type)
Expand Down Expand Up @@ -244,30 +245,30 @@ private List<DataProperty> GetDataPropertiesFor(Type objectType, out Type extens

private static readonly Dictionary<Type, Tuple<DataType, string>> PrimitiveTypesAndFormats = new()
{
[ typeof(bool) ] = Tuple.Create(DataType.Boolean, (string)null),
[ typeof(byte) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(sbyte) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(short) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(ushort) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(int) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(uint) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(long) ] = Tuple.Create(DataType.Integer, "int64"),
[ typeof(ulong) ] = Tuple.Create(DataType.Integer, "int64"),
[ typeof(float) ] = Tuple.Create(DataType.Number, "float"),
[ typeof(double) ] = Tuple.Create(DataType.Number, "double"),
[ typeof(decimal) ] = Tuple.Create(DataType.Number, "double"),
[ typeof(byte[]) ] = Tuple.Create(DataType.String, "byte"),
[ typeof(string) ] = Tuple.Create(DataType.String, (string)null),
[ typeof(char) ] = Tuple.Create(DataType.String, (string)null),
[ typeof(DateTime) ] = Tuple.Create(DataType.String, "date-time"),
[ typeof(DateTimeOffset) ] = Tuple.Create(DataType.String, "date-time"),
[ typeof(TimeSpan) ] = Tuple.Create(DataType.String, "date-span"),
[ typeof(Guid) ] = Tuple.Create(DataType.String, "uuid"),
[ typeof(Uri) ] = Tuple.Create(DataType.String, "uri"),
[ typeof(Version) ] = Tuple.Create(DataType.String, (string)null),
[typeof(bool)] = Tuple.Create(DataType.Boolean, (string)null),
[typeof(byte)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(sbyte)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(short)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(ushort)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(int)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(uint)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(long)] = Tuple.Create(DataType.Integer, "int64"),
[typeof(ulong)] = Tuple.Create(DataType.Integer, "int64"),
[typeof(float)] = Tuple.Create(DataType.Number, "float"),
[typeof(double)] = Tuple.Create(DataType.Number, "double"),
[typeof(decimal)] = Tuple.Create(DataType.Number, "double"),
[typeof(byte[])] = Tuple.Create(DataType.String, "byte"),
[typeof(string)] = Tuple.Create(DataType.String, (string)null),
[typeof(char)] = Tuple.Create(DataType.String, (string)null),
[typeof(DateTime)] = Tuple.Create(DataType.String, "date-time"),
[typeof(DateTimeOffset)] = Tuple.Create(DataType.String, "date-time"),
[typeof(TimeSpan)] = Tuple.Create(DataType.String, "date-span"),
[typeof(Guid)] = Tuple.Create(DataType.String, "uuid"),
[typeof(Uri)] = Tuple.Create(DataType.String, "uri"),
[typeof(Version)] = Tuple.Create(DataType.String, (string)null),
#if NET6_0_OR_GREATER
[ typeof(DateOnly) ] = Tuple.Create(DataType.String, "date"),
[ typeof(TimeOnly) ] = Tuple.Create(DataType.String, "time"),
[typeof(DateOnly)] = Tuple.Create(DataType.String, "date"),
[typeof(TimeOnly)] = Tuple.Create(DataType.String, "time"),
#endif
#if NET7_0_OR_GREATER
[ typeof(Int128) ] = Tuple.Create(DataType.Integer, "int128"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ private OpenApiSchema GenerateSchemaForType(Type modelType, SchemaRepository sch

private DataContract GetDataContractFor(Type modelType)
{
var effectiveType = Nullable.GetUnderlyingType(modelType) ?? modelType;
return _serializerDataContractResolver.GetDataContractForType(effectiveType);
return _serializerDataContractResolver.GetDataContractForType(modelType);
}

private bool IsBaseTypeWithKnownTypesDefined(DataContract dataContract, out IEnumerable<DataContract> knownTypesDataContracts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Xunit;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.TestSupport;
using Xunit;

namespace Swashbuckle.AspNetCore.Newtonsoft.Test
{
Expand Down Expand Up @@ -273,6 +273,8 @@ public void GenerateSchema_SetsNullableFlag_IfPropertyIsReferenceOrNullableType(
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.StringWithDefault), "\"foobar\"")]
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.IntArrayWithDefault), "[\n 1,\n 2,\n 3\n]")]
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.StringArrayWithDefault), "[\n \"foo\",\n \"bar\"\n]")]
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.NullableIntWithDefaultNullValue), "null")]
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.NullableIntWithDefaultValue), "2147483647")]
[UseInvariantCulture]
public void GenerateSchema_SetsDefault_IfPropertyHasDefaultValueAttribute(
Type declaringType,
Expand Down Expand Up @@ -505,7 +507,7 @@ public void GenerateSchema_SupportsOption_UseAllOfForPolymorphism()
Assert.NotNull(schema.OneOf[0].Reference);
var baseSchema = schemaRepository.Schemas[schema.OneOf[0].Reference.Id];
Assert.Equal("object", baseSchema.Type);
Assert.Equal(new[] { "BaseProperty"}, baseSchema.Properties.Keys);
Assert.Equal(new[] { "BaseProperty" }, baseSchema.Properties.Keys);
// The first sub type schema
Assert.NotNull(schema.OneOf[1].Reference);
var subType1Schema = schemaRepository.Schemas[schema.OneOf[1].Reference.Id];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.TestSupport;
using Swashbuckle.AspNetCore.SwaggerGen.Test.Fixtures;
using Swashbuckle.AspNetCore.TestSupport;
using Xunit;

namespace Swashbuckle.AspNetCore.SwaggerGen.Test
Expand Down Expand Up @@ -296,6 +296,8 @@ public void GenerateSchema_SetsNullableFlag_IfPropertyIsReferenceOrNullableType(
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.StringWithDefault), "\"foobar\"")]
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.IntArrayWithDefault), "[\n 1,\n 2,\n 3\n]")]
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.StringArrayWithDefault), "[\n \"foo\",\n \"bar\"\n]")]
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.NullableIntWithDefaultNullValue), "null")]
[InlineData(typeof(TypeWithDefaultAttributes), nameof(TypeWithDefaultAttributes.NullableIntWithDefaultValue), "2147483647")]
[UseInvariantCulture]
public void GenerateSchema_SetsDefault_IfPropertyHasDefaultValueAttribute(
Type declaringType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,11 @@ public class TypeWithDefaultAttributes

[DefaultValue(new[] { "foo", "bar" })]
public string[] StringArrayWithDefault { get; set; }

[DefaultValue(null)]
public int? NullableIntWithDefaultNullValue { get; set; }

[DefaultValue(int.MaxValue)]
public int? NullableIntWithDefaultValue { get; set; }
}
}
Loading