Skip to content
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 @@ -186,11 +186,15 @@ private void LoadPropertyOrField(JsonProperty jsonProperty, ContextualAccessorIn
parentSchema.RequiredProperties.Add(propertyName);
}

var isNullable = propertyTypeDescription.IsNullable && !hasRequiredAttribute && jsonProperty.Required is Required.Default or Required.AllowNull;
// The C# required keyword marks a property as required in the schema's required array
// but does not imply a non-nullable value. Only [Required] carries the semantic meaning
// of "non-null value required" and should suppress nullability and trigger MinLength = 1
// on strings.
var isNullable = propertyTypeDescription.IsNullable && requiredAttribute == null && jsonProperty.Required is Required.Default or Required.AllowNull;

var defaultValue = jsonProperty.DefaultValue;

schemaGenerator.AddProperty(parentSchema, accessorInfo, propertyTypeDescription, propertyName, requiredAttribute, hasRequiredAttribute, isNullable, defaultValue, schemaResolver);
schemaGenerator.AddProperty(parentSchema, accessorInfo, propertyTypeDescription, propertyName, requiredAttribute, requiredAttribute != null, isNullable, defaultValue, schemaResolver);
}
}

Expand Down
22 changes: 22 additions & 0 deletions src/NJsonSchema.Tests/Generation/AttributeGenerationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,28 @@ public void When_property_has_required_keyword_then_it_is_required_in_Newtonsoft
Assert.Contains("Name", schema.RequiredProperties);
Assert.DoesNotContain("Optional", schema.RequiredProperties);
}

#nullable enable
public class ClassWithRequiredNullableKeyword
{
public required string? Name { get; set; }
public string? Optional { get; set; }
}
#nullable restore

[Fact]
public void When_property_has_required_keyword_and_nullable_type_then_it_is_required_and_nullable_in_Newtonsoft_schema()
{
// Act
var schema = NewtonsoftJsonSchemaGenerator.FromType<ClassWithRequiredNullableKeyword>();

// Assert: required keyword adds to required array
Assert.Contains("Name", schema.RequiredProperties);
Assert.DoesNotContain("Optional", schema.RequiredProperties);
// Assert: nullable type is preserved — required keyword alone must not suppress nullability
Assert.True(schema.Properties["Name"].IsNullable(SchemaType.JsonSchema));
Assert.Null(schema.Properties["Name"].MinLength);
}
#endif

#if NET6_0_OR_GREATER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,28 @@ public void When_property_has_required_keyword_then_it_is_required_in_schema()
Assert.DoesNotContain("Optional", schema.RequiredProperties);
}

#nullable enable
public class ClassWithRequiredNullableKeyword
{
public required string? Name { get; set; }
public string? Optional { get; set; }
}
#nullable restore

[Fact]
public void When_property_has_required_keyword_and_nullable_type_then_it_is_required_and_nullable_in_schema()
{
// Act
var schema = JsonSchema.FromType<ClassWithRequiredNullableKeyword>();

// Assert: required keyword adds to required array
Assert.Contains("Name", schema.RequiredProperties);
Assert.DoesNotContain("Optional", schema.RequiredProperties);
// Assert: nullable type is preserved — required keyword alone must not suppress nullability
Assert.True(schema.Properties["Name"].IsNullable(SchemaType.JsonSchema));
Assert.Null(schema.Properties["Name"].MinLength);
}

public class ClassWithJsonRequired
{
[System.Text.Json.Serialization.JsonRequired]
Expand All @@ -168,6 +190,30 @@ public void When_property_has_JsonRequired_then_it_is_required_in_schema()
Assert.Contains("Name", schema.RequiredProperties);
Assert.DoesNotContain("Optional", schema.RequiredProperties);
}

#nullable enable
public class ClassWithJsonRequiredNullable
{
[System.Text.Json.Serialization.JsonRequired]
public string? Name { get; set; }
public string? Optional { get; set; }
}
#nullable restore

[Fact]
public void When_property_has_JsonRequired_and_nullable_type_then_it_is_required_and_nullable_in_schema()
{
// Act
var schema = JsonSchema.FromType<ClassWithJsonRequiredNullable>();

// Assert: [JsonRequired] is a presence marker (like the C# required keyword) and matches
// System.Text.Json runtime semantics — the property must be present in the JSON, but the
// value may be null. Only [Required] (DataAnnotations) implies a non-null value.
Assert.Contains("Name", schema.RequiredProperties);
Assert.DoesNotContain("Optional", schema.RequiredProperties);
Assert.True(schema.Properties["Name"].IsNullable(SchemaType.JsonSchema));
Assert.Null(schema.Properties["Name"].MinLength);
}
#endif

public class ClassWithPublicField
Expand Down
8 changes: 6 additions & 2 deletions src/NJsonSchema/Generation/SystemTextJsonReflectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,14 @@ public override void GenerateProperties(JsonSchema schema, ContextualType contex
schema.RequiredProperties.Add(propertyName);
}

var isNullable = propertyTypeDescription.IsNullable && !hasRequiredAttribute;
// Presence markers (C# required keyword, [JsonRequired], DataMember.IsRequired) only
// mark a property as required in the schema's required array. Only [Required] carries
// the semantic meaning of "non-null value required" and should suppress nullability
// and trigger MinLength = 1 on strings.
var isNullable = propertyTypeDescription.IsNullable && requiredAttribute == null;

// TODO: Add default value
schemaGenerator.AddProperty(schema, accessorInfo, propertyTypeDescription, propertyName, requiredAttribute, hasRequiredAttribute, isNullable, null, schemaResolver);
schemaGenerator.AddProperty(schema, accessorInfo, propertyTypeDescription, propertyName, requiredAttribute, requiredAttribute != null, isNullable, null, schemaResolver);
}
}
}
Expand Down
Loading