Skip to content

Commit 33f0762

Browse files
Copilotlahma
andcommitted
Add support for const keyword in JSON Schema
Co-authored-by: lahma <171892+lahma@users.noreply.github.com>
1 parent 9c2a103 commit 33f0762

File tree

11 files changed

+341
-5
lines changed

11 files changed

+341
-5
lines changed

src/NJsonSchema.CodeGeneration.CSharp.Tests/DefaultPropertyTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,5 +200,59 @@ public async Task When_generating_CSharp_code_then_default_value_of_array_of_arr
200200
await VerifyHelper.Verify(code);
201201
CSharpCompiler.AssertCompile(code);
202202
}
203+
204+
[Fact]
205+
public async Task When_property_has_const_string_value_then_property_is_readonly_with_default_value()
206+
{
207+
// Arrange
208+
var data = @"{
209+
""type"": ""object"",
210+
""properties"": {
211+
""cmdType"": {
212+
""const"": ""person""
213+
}
214+
}
215+
}";
216+
217+
var schema = await JsonSchema.FromJsonAsync(data);
218+
var settings = new CSharpGeneratorSettings
219+
{
220+
ClassStyle = CSharpClassStyle.Poco,
221+
Namespace = "ns"
222+
};
223+
var gen = new CSharpGenerator(schema, settings);
224+
var output = gen.GenerateFile("MyClass");
225+
226+
// Assert
227+
await VerifyHelper.Verify(output);
228+
CSharpCompiler.AssertCompile(output);
229+
}
230+
231+
[Fact]
232+
public async Task When_property_has_const_integer_value_then_property_is_readonly_with_default_value()
233+
{
234+
// Arrange
235+
var data = @"{
236+
""type"": ""object"",
237+
""properties"": {
238+
""myNumber"": {
239+
""const"": 42
240+
}
241+
}
242+
}";
243+
244+
var schema = await JsonSchema.FromJsonAsync(data);
245+
var settings = new CSharpGeneratorSettings
246+
{
247+
ClassStyle = CSharpClassStyle.Poco,
248+
Namespace = "ns"
249+
};
250+
var gen = new CSharpGenerator(schema, settings);
251+
var output = gen.GenerateFile("MyClass");
252+
253+
// Assert
254+
await VerifyHelper.Verify(output);
255+
CSharpCompiler.AssertCompile(output);
256+
}
203257
}
204258
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//----------------------
2+
// <auto-generated>
3+
// </auto-generated>
4+
//----------------------
5+
6+
7+
namespace ns
8+
{
9+
#pragma warning disable // Disable all warnings
10+
11+
public partial class MyClass
12+
{
13+
14+
[Newtonsoft.Json.JsonProperty("myNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
15+
public int MyNumber { get; } = 42;
16+
17+
private System.Collections.Generic.IDictionary<string, object> _additionalProperties;
18+
19+
[Newtonsoft.Json.JsonExtensionData]
20+
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
21+
{
22+
get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
23+
set { _additionalProperties = value; }
24+
}
25+
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//----------------------
2+
// <auto-generated>
3+
// </auto-generated>
4+
//----------------------
5+
6+
7+
namespace ns
8+
{
9+
#pragma warning disable // Disable all warnings
10+
11+
public partial class MyClass
12+
{
13+
14+
[Newtonsoft.Json.JsonProperty("cmdType", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
15+
public string CmdType { get; } = "person";
16+
17+
private System.Collections.Generic.IDictionary<string, object> _additionalProperties;
18+
19+
[Newtonsoft.Json.JsonExtensionData]
20+
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
21+
{
22+
get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
23+
set { _additionalProperties = value; }
24+
}
25+
26+
}
27+
}

src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ schema is JsonSchemaProperty property &&
8181
schema.InheritedSchema == null && // not in inheritance hierarchy
8282
schema.AllOf.Count == 0 &&
8383
!Types.ContainsKey(schema) &&
84-
!schema.HasReference)
84+
!schema.HasReference &&
85+
!schema.ActualTypeSchema.HasConstValue)
8586
{
8687
return markAsNullableType ? Settings.AnyType + "?" : Settings.AnyType;
8788
}
@@ -94,6 +95,11 @@ schema is JsonSchemaProperty property &&
9495
: JsonObjectType.String;
9596
}
9697

98+
if (type == JsonObjectType.None && schema.ActualTypeSchema.HasConstValue)
99+
{
100+
type = schema.ActualTypeSchema.ConstValueType;
101+
}
102+
97103
if (type.IsNumber())
98104
{
99105
return ResolveNumber(schema.ActualTypeSchema, isNullable);

src/NJsonSchema.CodeGeneration.CSharp/Models/PropertyModel.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ public PropertyModel(
7171

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

7778
/// <summary>Gets the json property required.</summary>
7879
public string JsonPropertyRequiredCode

src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ private string Resolve(JsonSchema schema, string? typeNameHint, bool addInterfac
8989
schema.InheritedSchema == null && // not in inheritance hierarchy
9090
schema.AllOf.Count == 0 &&
9191
!Types.ContainsKey(schema) &&
92-
!schema.HasReference)
92+
!schema.HasReference &&
93+
!schema.ActualTypeSchema.HasConstValue)
9394
{
9495
return "any";
9596
}
@@ -102,6 +103,11 @@ private string Resolve(JsonSchema schema, string? typeNameHint, bool addInterfac
102103
JsonObjectType.String;
103104
}
104105

106+
if (schema.ActualTypeSchema.HasConstValue)
107+
{
108+
return ResolveConst(schema.ActualTypeSchema);
109+
}
110+
105111
if (type.IsNumber())
106112
{
107113
return "number";
@@ -296,6 +302,22 @@ private static string ResolveInteger(JsonSchema schema, string? typeNameHint)
296302
return "number";
297303
}
298304

305+
private static string ResolveConst(JsonSchema schema)
306+
{
307+
var constType = schema.ConstValueType;
308+
if (constType.IsBoolean())
309+
{
310+
return schema.Const!.ToString()!.ToLowerInvariant();
311+
}
312+
313+
if (constType.IsInteger() || constType.IsNumber())
314+
{
315+
return schema.Const!.ToString()!;
316+
}
317+
318+
return ConversionUtilities.ConvertToStringLiteral(schema.Const!.ToString() ?? string.Empty, "\"", "\"");
319+
}
320+
299321
private string ResolveArrayOrTuple(JsonSchema schema, string? typeNameHint, bool addInterfacePrefix)
300322
{
301323
if (schema.Item != null)

src/NJsonSchema.CodeGeneration/ValueGeneratorBase.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,19 @@ protected ValueGeneratorBase(CodeGeneratorSettingsBase settings)
4848
/// <returns>The code.</returns>
4949
public virtual string? GetDefaultValue(JsonSchema schema, bool allowsNull, string targetType, string? typeNameHint, bool useSchemaDefault, TypeResolverBase typeResolver)
5050
{
51+
var actualSchema = schema is JsonSchemaProperty ? ((JsonSchemaProperty)schema).ActualTypeSchema : schema.ActualSchema;
52+
53+
// Const values are always used as default values (they define the only valid value)
54+
if (actualSchema.HasConstValue)
55+
{
56+
return GetConstantValue(actualSchema, targetType);
57+
}
58+
5159
if (schema.Default == null || !useSchemaDefault)
5260
{
5361
return null;
5462
}
5563

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

124+
/// <summary>Gets the constant value code for a schema with a const value.</summary>
125+
/// <param name="schema">The schema with a const value.</param>
126+
/// <param name="targetType">The target type name.</param>
127+
/// <returns>The code representing the constant value, or null if not applicable.</returns>
128+
protected virtual string? GetConstantValue(JsonSchema schema, string targetType)
129+
{
130+
if (!schema.HasConstValue)
131+
{
132+
return null;
133+
}
134+
135+
var constType = schema.ConstValueType;
136+
if (constType.IsBoolean())
137+
{
138+
return schema.Const!.ToString()!.ToLowerInvariant();
139+
}
140+
141+
if (constType.IsInteger() || constType.IsNumber())
142+
{
143+
return schema.Const!.ToString();
144+
}
145+
146+
return ConversionUtilities.ConvertToStringLiteral(schema.Const!.ToString() ?? string.Empty, "\"", "\"");
147+
}
148+
117149
/// <summary>Converts a number to its string representation.</summary>
118150
/// <param name="value">The value.</param>
119151
/// <returns>The string.</returns>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using NJsonSchema.Validation;
2+
3+
namespace NJsonSchema.Tests.Validation
4+
{
5+
public class ConstValidationTests
6+
{
7+
[Fact]
8+
public async Task When_const_is_defined_and_value_matches_then_validation_succeeds()
9+
{
10+
// Arrange
11+
var json = @"{ ""const"": ""person"" }";
12+
var schema = await JsonSchema.FromJsonAsync(json);
13+
14+
// Act
15+
var errors = schema.Validate(@"""person""");
16+
17+
// Assert
18+
Assert.Empty(errors);
19+
}
20+
21+
[Fact]
22+
public async Task When_const_is_defined_and_value_does_not_match_then_validation_fails()
23+
{
24+
// Arrange
25+
var json = @"{ ""const"": ""person"" }";
26+
var schema = await JsonSchema.FromJsonAsync(json);
27+
28+
// Act
29+
var errors = schema.Validate(@"""other""");
30+
31+
// Assert
32+
Assert.Single(errors);
33+
Assert.Equal(ValidationErrorKind.ConstantValueMismatch, errors.First().Kind);
34+
}
35+
36+
[Fact]
37+
public async Task When_const_is_integer_and_value_matches_then_validation_succeeds()
38+
{
39+
// Arrange
40+
var json = @"{ ""const"": 42 }";
41+
var schema = await JsonSchema.FromJsonAsync(json);
42+
43+
// Act
44+
var errors = schema.Validate("42");
45+
46+
// Assert
47+
Assert.Empty(errors);
48+
}
49+
50+
[Fact]
51+
public async Task When_const_is_integer_and_value_does_not_match_then_validation_fails()
52+
{
53+
// Arrange
54+
var json = @"{ ""const"": 42 }";
55+
var schema = await JsonSchema.FromJsonAsync(json);
56+
57+
// Act
58+
var errors = schema.Validate("99");
59+
60+
// Assert
61+
Assert.Single(errors);
62+
Assert.Equal(ValidationErrorKind.ConstantValueMismatch, errors.First().Kind);
63+
}
64+
65+
[Fact]
66+
public void When_schema_is_serialized_with_const_then_const_is_included()
67+
{
68+
// Arrange
69+
var schema = new JsonSchema
70+
{
71+
Const = "person"
72+
};
73+
74+
// Act
75+
var json = schema.ToJson();
76+
77+
// Assert
78+
Assert.Contains("\"const\"", json);
79+
Assert.Contains("\"person\"", json);
80+
}
81+
82+
[Fact]
83+
public async Task When_schema_with_const_is_deserialized_then_const_is_populated()
84+
{
85+
// Arrange
86+
var json = @"{
87+
""type"": ""object"",
88+
""properties"": {
89+
""cmdType"": { ""const"": ""person"" }
90+
}
91+
}";
92+
93+
// Act
94+
var schema = await JsonSchema.FromJsonAsync(json);
95+
96+
// Assert
97+
Assert.True(schema.Properties["cmdType"].HasConstValue);
98+
Assert.Equal("person", schema.Properties["cmdType"].Const?.ToString());
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)