Skip to content
Open
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 @@ -200,5 +200,59 @@ public async Task When_generating_CSharp_code_then_default_value_of_array_of_arr
await VerifyHelper.Verify(code);
CSharpCompiler.AssertCompile(code);
}

[Fact]
public async Task When_property_has_const_string_value_then_property_is_readonly_with_default_value()
{
// Arrange
var data = @"{
""type"": ""object"",
""properties"": {
""cmdType"": {
""const"": ""person""
}
}
}";

var schema = await JsonSchema.FromJsonAsync(data);
var settings = new CSharpGeneratorSettings
{
ClassStyle = CSharpClassStyle.Poco,
Namespace = "ns"
};
var gen = new CSharpGenerator(schema, settings);
var output = gen.GenerateFile("MyClass");

// Assert
await VerifyHelper.Verify(output);
CSharpCompiler.AssertCompile(output);
}

[Fact]
public async Task When_property_has_const_integer_value_then_property_is_readonly_with_default_value()
{
// Arrange
var data = @"{
""type"": ""object"",
""properties"": {
""myNumber"": {
""const"": 42
}
}
}";

var schema = await JsonSchema.FromJsonAsync(data);
var settings = new CSharpGeneratorSettings
{
ClassStyle = CSharpClassStyle.Poco,
Namespace = "ns"
};
var gen = new CSharpGenerator(schema, settings);
var output = gen.GenerateFile("MyClass");

// Assert
await VerifyHelper.Verify(output);
CSharpCompiler.AssertCompile(output);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//----------------------
// <auto-generated>
// </auto-generated>
//----------------------


namespace ns
{
#pragma warning disable // Disable all warnings

public partial class MyClass
{

[Newtonsoft.Json.JsonProperty("myNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public int MyNumber { get; } = 42;

private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

[Newtonsoft.Json.JsonExtensionData]
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
set { _additionalProperties = value; }
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//----------------------
// <auto-generated>
// </auto-generated>
//----------------------


namespace ns
{
#pragma warning disable // Disable all warnings

public partial class MyClass
{

[Newtonsoft.Json.JsonProperty("cmdType", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string CmdType { get; } = "person";

private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

[Newtonsoft.Json.JsonExtensionData]
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
set { _additionalProperties = value; }
}

}
}
8 changes: 7 additions & 1 deletion src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ schema is JsonSchemaProperty property &&
schema.InheritedSchema == null && // not in inheritance hierarchy
schema.AllOf.Count == 0 &&
!Types.ContainsKey(schema) &&
!schema.HasReference)
!schema.HasReference &&
!schema.ActualTypeSchema.HasConstValue)
{
return markAsNullableType ? Settings.AnyType + "?" : Settings.AnyType;
}
Expand All @@ -94,6 +95,11 @@ schema is JsonSchemaProperty property &&
: JsonObjectType.String;
}

if (type == JsonObjectType.None && schema.ActualTypeSchema.HasConstValue)
{
type = schema.ActualTypeSchema.ConstValueType;
}

if (type.IsNumber())
{
return ResolveNumber(schema.ActualTypeSchema, isNullable);
Expand Down
5 changes: 3 additions & 2 deletions src/NJsonSchema.CodeGeneration.CSharp/Models/PropertyModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ public PropertyModel(

/// <summary>Gets a value indicating whether this is an array property which cannot be null.</summary>
public bool HasSetter =>
_property.IsNullable(_settings.SchemaType) || (!_property.ActualTypeSchema.IsArray || !_settings.GenerateImmutableArrayProperties) &&
(!_property.ActualTypeSchema.IsDictionary || !_settings.GenerateImmutableDictionaryProperties);
!_property.ActualSchema.HasConstValue &&
(_property.IsNullable(_settings.SchemaType) || (!_property.ActualTypeSchema.IsArray || !_settings.GenerateImmutableArrayProperties) &&
(!_property.ActualTypeSchema.IsDictionary || !_settings.GenerateImmutableDictionaryProperties));

/// <summary>Gets the json property required.</summary>
public string JsonPropertyRequiredCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ private string Resolve(JsonSchema schema, string? typeNameHint, bool addInterfac
schema.InheritedSchema == null && // not in inheritance hierarchy
schema.AllOf.Count == 0 &&
!Types.ContainsKey(schema) &&
!schema.HasReference)
!schema.HasReference &&
!schema.ActualTypeSchema.HasConstValue)
{
return "any";
}
Expand All @@ -102,6 +103,11 @@ private string Resolve(JsonSchema schema, string? typeNameHint, bool addInterfac
JsonObjectType.String;
}

if (schema.ActualTypeSchema.HasConstValue)
{
return ResolveConst(schema.ActualTypeSchema);
}

if (type.IsNumber())
{
return "number";
Expand Down Expand Up @@ -296,6 +302,22 @@ private static string ResolveInteger(JsonSchema schema, string? typeNameHint)
return "number";
}

private static string ResolveConst(JsonSchema schema)
{
var constType = schema.ConstValueType;
if (constType.IsBoolean())
{
return schema.Const!.ToString()!.ToLowerInvariant();
}

if (constType.IsInteger() || constType.IsNumber())
{
return ValueGeneratorBase.ConvertToNumberToStringCore(schema.Const!);
}

return ConversionUtilities.ConvertToStringLiteral(schema.Const!.ToString() ?? string.Empty, "\"", "\"");
}

private string ResolveArrayOrTuple(JsonSchema schema, string? typeNameHint, bool addInterfacePrefix)
{
if (schema.Item != null)
Expand Down
34 changes: 33 additions & 1 deletion src/NJsonSchema.CodeGeneration/ValueGeneratorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,19 @@ protected ValueGeneratorBase(CodeGeneratorSettingsBase settings)
/// <returns>The code.</returns>
public virtual string? GetDefaultValue(JsonSchema schema, bool allowsNull, string targetType, string? typeNameHint, bool useSchemaDefault, TypeResolverBase typeResolver)
{
var actualSchema = schema is JsonSchemaProperty ? ((JsonSchemaProperty)schema).ActualTypeSchema : schema.ActualSchema;

// Const values are always used as default values (they define the only valid value)
if (actualSchema.HasConstValue)
{
return GetConstantValue(actualSchema, targetType);
}

if (schema.Default == null || !useSchemaDefault)
{
return null;
}

var actualSchema = schema is JsonSchemaProperty ? ((JsonSchemaProperty)schema).ActualTypeSchema : schema.ActualSchema;
if (actualSchema.IsEnumeration && !actualSchema.Type.IsObject() && actualSchema.Type != JsonObjectType.None)
{
return GetEnumDefaultValue(schema, actualSchema, typeNameHint, typeResolver);
Expand Down Expand Up @@ -114,6 +121,31 @@ protected string GetDefaultAsStringLiteral(JsonSchema schema)
return ConversionUtilities.ConvertToStringLiteral(schema.Default?.ToString() ?? string.Empty, "\"", "\"");
}

/// <summary>Gets the constant value code for a schema with a const value.</summary>
/// <param name="schema">The schema with a const value.</param>
/// <param name="targetType">The target type name.</param>
/// <returns>The code representing the constant value, or null if not applicable.</returns>
protected virtual string? GetConstantValue(JsonSchema schema, string targetType)
{
if (!schema.HasConstValue)
{
return null;
}

var constType = schema.ConstValueType;
if (constType.IsBoolean())
{
return schema.Const!.ToString()!.ToLowerInvariant();
}

if (constType.IsInteger() || constType.IsNumber())
{
return ConvertToNumberToStringCore(schema.Const!);
}

return ConversionUtilities.ConvertToStringLiteral(schema.Const!.ToString() ?? string.Empty, "\"", "\"");
}

/// <summary>Converts a number to its string representation.</summary>
/// <param name="value">The value.</param>
/// <returns>The string.</returns>
Expand Down
101 changes: 101 additions & 0 deletions src/NJsonSchema.Tests/Validation/ConstValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using NJsonSchema.Validation;

namespace NJsonSchema.Tests.Validation
{
public class ConstValidationTests
{
[Fact]
public async Task When_const_is_defined_and_value_matches_then_validation_succeeds()
{
// Arrange
var json = @"{ ""const"": ""person"" }";
var schema = await JsonSchema.FromJsonAsync(json);

// Act
var errors = schema.Validate(@"""person""");

// Assert
Assert.Empty(errors);
}

[Fact]
public async Task When_const_is_defined_and_value_does_not_match_then_validation_fails()
{
// Arrange
var json = @"{ ""const"": ""person"" }";
var schema = await JsonSchema.FromJsonAsync(json);

// Act
var errors = schema.Validate(@"""other""");

// Assert
Assert.Single(errors);
Assert.Equal(ValidationErrorKind.ConstantValueMismatch, errors.First().Kind);
}

[Fact]
public async Task When_const_is_integer_and_value_matches_then_validation_succeeds()
{
// Arrange
var json = @"{ ""const"": 42 }";
var schema = await JsonSchema.FromJsonAsync(json);

// Act
var errors = schema.Validate("42");

// Assert
Assert.Empty(errors);
}

[Fact]
public async Task When_const_is_integer_and_value_does_not_match_then_validation_fails()
{
// Arrange
var json = @"{ ""const"": 42 }";
var schema = await JsonSchema.FromJsonAsync(json);

// Act
var errors = schema.Validate("99");

// Assert
Assert.Single(errors);
Assert.Equal(ValidationErrorKind.ConstantValueMismatch, errors.First().Kind);
}

[Fact]
public void When_schema_is_serialized_with_const_then_const_is_included()
{
// Arrange
var schema = new JsonSchema
{
Const = "person"
};

// Act
var json = schema.ToJson();

// Assert
Assert.Contains("\"const\"", json);
Assert.Contains("\"person\"", json);
}

[Fact]
public async Task When_schema_with_const_is_deserialized_then_const_is_populated()
{
// Arrange
var json = @"{
""type"": ""object"",
""properties"": {
""cmdType"": { ""const"": ""person"" }
}
}";

// Act
var schema = await JsonSchema.FromJsonAsync(json);

// Assert
Assert.True(schema.Properties["cmdType"].HasConstValue);
Assert.Equal("person", schema.Properties["cmdType"].Const?.ToString());
}
}
}
Loading