Skip to content

Commit 2c5ec58

Browse files
authored
Support [DataMember] IsRequired in NewtonsoftDataContractResolver (#2644)
* ✅ Add non-regression tests for DataMember required As DataMember implementation comes after the implementation for [JsonProperty] and [JsonObject], we want to be sure that the current behavior is not bypassed. * ✨Add support for [DataMember(IsRequired)] Add test cases for DataMember (IsRequired and Name). It seems that NewtonSoft's ResolveContract correctly set the Required, using [DataMember], [JsonProperpty] and [JsonRequired] so we can directly use it instead of the IsRequiredSpecifed extension method. * ✨ Keep IsRequiredSpecified() but use JsonProperty.Required internally Newtonsoft do all the work for us, handling [JsonProperty(Required = Required.Always)] or [JsonRequired] or [DataMember(IsRequired = true)] and filling the JsonProperty.Required field.
1 parent 8f9c079 commit 2c5ec58

File tree

6 files changed

+92
-16
lines changed

6 files changed

+92
-16
lines changed

src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/JsonPropertyExtensions.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,7 @@ public static bool TryGetMemberInfo(this JsonProperty jsonProperty, out MemberIn
1717

1818
public static bool IsRequiredSpecified(this JsonProperty jsonProperty)
1919
{
20-
if (!jsonProperty.TryGetMemberInfo(out MemberInfo memberInfo))
21-
return false;
22-
23-
if (memberInfo.GetCustomAttribute<JsonRequiredAttribute>() != null)
24-
return true;
25-
26-
var jsonPropertyAttributeData = memberInfo.GetCustomAttributesData()
27-
.FirstOrDefault(attrData => attrData.AttributeType == typeof(JsonPropertyAttribute));
28-
29-
return (jsonPropertyAttributeData != null) && jsonPropertyAttributeData.NamedArguments.Any(arg => arg.MemberName == "Required");
20+
return jsonProperty.Required != Required.Default;
3021
}
3122
}
3223
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace Swashbuckle.AspNetCore.Newtonsoft.Test
4+
{
5+
[DataContract(Name = "CustomNameFromDataContract")]
6+
public class DataMemberAnnotatedType
7+
{
8+
[DataMember(IsRequired = true)]
9+
public string StringWithDataMemberRequired { get; set; }
10+
11+
[DataMember(IsRequired = false)]
12+
public string StringWithDataMemberNonRequired { get; set; }
13+
14+
[DataMember(IsRequired = true, Name = "RequiredWithCustomNameFromDataMember")]
15+
public string StringWithDataMemberRequiredWithCustomName { get; set; }
16+
17+
[DataMember(IsRequired = false, Name = "NonRequiredWithCustomNameFromDataMember")]
18+
public string StringWithDataMemberNonRequiredWithCustomName { get; set; }
19+
}
20+
}

test/Swashbuckle.AspNetCore.Newtonsoft.Test/Fixtures/JsonObjectAnnotatedType.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
using Newtonsoft.Json;
1+
using System.Runtime.Serialization;
2+
using Newtonsoft.Json;
23

34
namespace Swashbuckle.AspNetCore.Newtonsoft.Test
45
{
56
[JsonObject(ItemRequired = Required.Always)]
7+
[DataContract]
68
public class JsonObjectAnnotatedType
79
{
810
public string StringWithNoAnnotation { get; set; }
@@ -12,5 +14,8 @@ public class JsonObjectAnnotatedType
1214

1315
[JsonProperty(Required = Required.AllowNull)]
1416
public string StringWithRequiredAllowNull { get; set; }
17+
18+
[DataMember(IsRequired = false)]
19+
public string StringWithDataMemberRequiredFalse { get; set; }
1520
}
1621
}

test/Swashbuckle.AspNetCore.Newtonsoft.Test/Fixtures/JsonPropertyAnnotatedType.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using Newtonsoft.Json;
1+
using System.Runtime.Serialization;
2+
using Newtonsoft.Json;
23

34
namespace Swashbuckle.AspNetCore.Newtonsoft.Test
45
{
6+
[DataContract]
57
public class JsonPropertyAnnotatedType
68
{
79
[JsonProperty("string-with-json-property-name")]
@@ -21,5 +23,13 @@ public class JsonPropertyAnnotatedType
2123

2224
[JsonProperty(Required = Required.AllowNull)]
2325
public string StringWithRequiredAllowNull { get; set; }
26+
27+
[DataMember(IsRequired = false)] //As the support for DataMember has been implemented later, JsonProperty.Required should take precedence
28+
[JsonProperty(Required = Required.Always)]
29+
public string StringWithRequiredAlwaysButConflictingDataMember { get; set; }
30+
31+
[DataMember(IsRequired = true)] //As the support for DataMember has been implemented later, JsonProperty.Required should take precedence
32+
[JsonProperty(Required = Required.Default)]
33+
public string StringWithRequiredDefaultButConflictingDataMember { get; set; }
2434
}
25-
}
35+
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
using Newtonsoft.Json;
1+
using System.Runtime.Serialization;
2+
using Newtonsoft.Json;
23

34
namespace Swashbuckle.AspNetCore.Newtonsoft.Test
45
{
6+
[DataContract]
57
public class JsonRequiredAnnotatedType
68
{
79
[JsonRequired]
810
public string StringWithJsonRequired { get; set; }
11+
12+
[DataMember(IsRequired = false)] //As the support for DataMember has been implemented later, JsonRequired should take precedence
13+
[JsonRequired]
14+
public string StringWithConflictingRequired { get; set; }
915
}
1016
}

test/Swashbuckle.AspNetCore.Newtonsoft.Test/SchemaGenerator/NewtonsoftSchemaGeneratorTests.cs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -755,14 +755,17 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonProperty()
755755
"StringWithRequiredDisallowNull",
756756
"StringWithRequiredAlways",
757757
"StringWithRequiredAllowNull",
758+
"StringWithRequiredAlwaysButConflictingDataMember",
759+
"StringWithRequiredDefaultButConflictingDataMember"
758760
},
759761
schema.Properties.Keys.ToArray()
760762
);
761763
Assert.Equal(
762764
new[]
763765
{
764766
"StringWithRequiredAllowNull",
765-
"StringWithRequiredAlways"
767+
"StringWithRequiredAlways",
768+
"StringWithRequiredAlwaysButConflictingDataMember"
766769
},
767770
schema.Required.ToArray()
768771
);
@@ -772,6 +775,8 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonProperty()
772775
Assert.False(schema.Properties["StringWithRequiredDisallowNull"].Nullable);
773776
Assert.False(schema.Properties["StringWithRequiredAlways"].Nullable);
774777
Assert.True(schema.Properties["StringWithRequiredAllowNull"].Nullable);
778+
Assert.False(schema.Properties["StringWithRequiredAlwaysButConflictingDataMember"].Nullable);
779+
Assert.True(schema.Properties["StringWithRequiredDefaultButConflictingDataMember"].Nullable);
775780
}
776781

777782
[Fact]
@@ -782,7 +787,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonRequired()
782787
var referenceSchema = Subject().GenerateSchema(typeof(JsonRequiredAnnotatedType), schemaRepository);
783788

784789
var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
785-
Assert.Equal(new[] { "StringWithJsonRequired" }, schema.Required.ToArray());
790+
Assert.Equal(new[] { "StringWithConflictingRequired", "StringWithJsonRequired"}, schema.Required.ToArray());
786791
Assert.False(schema.Properties["StringWithJsonRequired"].Nullable);
787792
}
788793

@@ -797,6 +802,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonObject()
797802
Assert.Equal(
798803
new[]
799804
{
805+
"StringWithDataMemberRequiredFalse",
800806
"StringWithNoAnnotation",
801807
"StringWithRequiredAllowNull",
802808
"StringWithRequiredUnspecified"
@@ -806,6 +812,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonObject()
806812
Assert.False(schema.Properties["StringWithNoAnnotation"].Nullable);
807813
Assert.False(schema.Properties["StringWithRequiredUnspecified"].Nullable);
808814
Assert.True(schema.Properties["StringWithRequiredAllowNull"].Nullable);
815+
Assert.False(schema.Properties["StringWithDataMemberRequiredFalse"].Nullable);
809816
}
810817

811818
[Fact]
@@ -821,6 +828,43 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonExtensionData()
821828
Assert.Null(schema.AdditionalProperties.Type);
822829
}
823830

831+
[Fact]
832+
public void GenerateSchema_HonorsDataMemberAttribute()
833+
{
834+
var schemaRepository = new SchemaRepository();
835+
836+
var referenceSchema = Subject().GenerateSchema(typeof(DataMemberAnnotatedType), schemaRepository);
837+
838+
var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
839+
840+
841+
Assert.True(schema.Properties["StringWithDataMemberRequired"].Nullable);
842+
Assert.True(schema.Properties["StringWithDataMemberNonRequired"].Nullable);
843+
Assert.True(schema.Properties["RequiredWithCustomNameFromDataMember"].Nullable);
844+
Assert.True(schema.Properties["NonRequiredWithCustomNameFromDataMember"].Nullable);
845+
846+
Assert.Equal(
847+
new[]
848+
{
849+
850+
"StringWithDataMemberRequired",
851+
"StringWithDataMemberNonRequired",
852+
"RequiredWithCustomNameFromDataMember",
853+
"NonRequiredWithCustomNameFromDataMember"
854+
},
855+
schema.Properties.Keys.ToArray()
856+
);
857+
858+
Assert.Equal(
859+
new[]
860+
{
861+
"RequiredWithCustomNameFromDataMember",
862+
"StringWithDataMemberRequired"
863+
},
864+
schema.Required.ToArray()
865+
);
866+
}
867+
824868
[Theory]
825869
[InlineData(typeof(ProblemDetails))]
826870
[InlineData(typeof(ValidationProblemDetails))]

0 commit comments

Comments
 (0)