Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
RicoSuter committed Jun 2, 2022
2 parents 005219a + d3a37e2 commit 71ce421
Show file tree
Hide file tree
Showing 20 changed files with 491 additions and 79 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NJsonSchema.CodeGeneration.CSharp;
Expand Down Expand Up @@ -141,7 +141,84 @@ public async Task When_using_SystemTextJson_additionalProperties_schema_is_set_f
// There are two matches, the Person class and the Pet class
Assert.Equal(2, matches.Count);
}

[Fact]
public async Task When_using_SystemTextJson_additionalProperties_schema_is_set_for_object_then_special_property_is_rendered_only_for_lowest_base_class()
{
var json =
@"{
""properties"": {
""Name"": {
""type"": ""string""
}
},
""definitions"": {
""Cat"": {
""allOf"": [
{
""$ref"": ""#/definitions/Pet""
},
{
""type"": ""object"",
""additionalProperties"": {
""nullable"": true
},
""properties"": {
""whiskers"": {
""type"": ""string""
}
}
}
]
},
""Pet"": {
""allOf"": [
{
""$ref"": ""#/definitions/Animal""
},
{
""type"": ""object"",
""additionalProperties"": {
""nullable"": true
},
""properties"": {
""id"": {
""type"": ""integer"",
""format"": ""int64""
}
}
}
]
},
""Animal"": {
""type"": ""object"",
""properties"": {
""category"": {
""type"": ""string"",
""nullable"": true
}
}
}
}
}";

var schema = await JsonSchema.FromJsonAsync(json);

//// Act
var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings()
{
JsonLibrary = CSharpJsonLibrary.SystemTextJson
});

var code = generator.GenerateFile("SommeDummyClass");

//// Assert
var matches = Regex.Matches(code, @"(\[System\.Text\.Json\.Serialization\.JsonExtensionData\])");

// There are two matches, the SommeDummyClass class and the Animal class
Assert.Equal(2, matches.Count);
}

public class Page
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,69 @@ public async Task When_generating_CSharp_code_then_default_value_with_decimal_ge
Assert.DoesNotContain("SomeOptionalProperty { get; set; } = D;", code);
Assert.Contains("double SomeOptionalProperty { get; set; } = 123.456D;", code);
}

[Fact]
public async Task When_generating_CSharp_code_then_default_value_of_dictionary_with_array_values_generates_expected_expression()
{
// Arrange
var document = await JsonSchema.FromJsonAsync(@"{
""type"": ""object"",
""required"": [""requiredDictionary""],
""properties"": {
""requiredDictionary"": {
""type"": ""object"",
""additionalProperties"": {
""type"": ""array"",
""items"": {
""type"": ""string""
}
}
}
}
}");

// Act
var settings = new CSharpGeneratorSettings();
settings.GenerateDefaultValues = true;

var generator = new CSharpGenerator(document, settings);
var code = generator.GenerateFile();

// Assert
Assert.DoesNotContain("System.Collections.Generic.IDictionary<string, System.Collections.Generic.ICollection<string>> RequiredDictionary { get; set; } = new System.Collections.Generic.Dictionary<string, System.Collections.ObjectModel.Collection<string>>();", code);
Assert.Contains("System.Collections.Generic.IDictionary<string, System.Collections.Generic.ICollection<string>> RequiredDictionary { get; set; } = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.ICollection<string>>();", code);
}

[Fact]
public async Task When_generating_CSharp_code_then_default_value_of_array_of_arrays_generates_expected_expression()
{
// Arrange
var document = await JsonSchema.FromJsonAsync(@"{
""type"": ""object"",
""required"": [""requiredList""],
""properties"": {
""requiredList"": {
""type"": ""array"",
""items"": {
""type"": ""array"",
""items"": {
""type"": ""string""
}
}
}
}
}");

// Act
var settings = new CSharpGeneratorSettings();
settings.GenerateDefaultValues = true;

var generator = new CSharpGenerator(document, settings);
var code = generator.GenerateFile();

// Assert
Assert.DoesNotContain("System.Collections.Generic.ICollection<System.Collections.Generic.ICollection<string>> RequiredList { get; set; } = new System.Collections.ObjectModel.Collection<System.Collections.ObjectModel.Collection<string>>();", code);
Assert.Contains("System.Collections.Generic.ICollection<System.Collections.Generic.ICollection<string>> RequiredList { get; set; } = new System.Collections.ObjectModel.Collection<System.Collections.Generic.ICollection<string>>();", code);
}
}
}
126 changes: 126 additions & 0 deletions src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,132 @@ public async Task When_property_is_required_then_CSharp_code_is_correct()

AssertCompile(code);
}

[Fact]
public async Task When_using_SystemTextJson_JsonIgnoreAttributes_are_generated_based_on_optionality() {
//// Arrange
var schema = await JsonSchema.FromJsonAsync(@"{
""type"": ""object"",
""required"": [""requiredValue"",""requiredRef""],
""properties"": {
""requiredValue"": { ""type"": ""integer"", ""format"": ""int32"" },
""requiredRef"": { ""type"": ""string"" },
""optionalValue"": { ""type"": ""integer"", ""format"": ""int32"" },
""optionalRef"": { ""type"": ""string"" }
}
}");

var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings {
JsonLibrary = CSharpJsonLibrary.SystemTextJson
});

static string Normalized(string str) =>
Regex.Replace(str, @"\s+", " ");

//// Act
var code = generator.GenerateFile("MyClass");

/// Assert
Assert.Contains(
Normalized(@"public int OptionalValue {"),
Normalized(code)
);

Assert.Contains(
Normalized(@"
[System.Text.Json.Serialization.JsonPropertyName(""requiredValue"")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)]
"),
Normalized(code)
);

Assert.Contains(
Normalized(@"
[System.Text.Json.Serialization.JsonPropertyName(""requiredRef"")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)]
"),
Normalized(code)
);

Assert.Contains(
Normalized(@"
[System.Text.Json.Serialization.JsonPropertyName(""optionalValue"")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
"),
Normalized(code)
);

Assert.Contains(
Normalized(@"
[System.Text.Json.Serialization.JsonPropertyName(""optionalRef"")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
"),
Normalized(code)
);
}

[Fact]
public async Task When_using_SystemTextJson_and_RequiredPropertiesMustBeDefined_is_false_JsonIgnoreAttributes_are_not_generated_for_required_properties() {
//// Arrange
var schema = await JsonSchema.FromJsonAsync(@"{
""type"": ""object"",
""required"": [""required""],
""properties"": {
""required"": { ""type"": ""string"" }
}
}");

var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings {
JsonLibrary = CSharpJsonLibrary.SystemTextJson,
RequiredPropertiesMustBeDefined = false
});

static string Normalized(string str) =>
Regex.Replace(str, @"\s+", " ");

//// Act
var code = generator.GenerateFile("MyClass");

/// Assert
Assert.DoesNotContain(
Normalized(@"
[System.Text.Json.Serialization.JsonIgnore
"),
Normalized(code)
);
}

[Fact]
public async Task When_using_SystemTextJson_and_RequiredPropertiesMustBeDefined_is_false_JsonIgnoreAttributes_are_still_generated_for_optional_properties() {
//// Arrange
var schema = await JsonSchema.FromJsonAsync(@"{
""type"": ""object"",
""required"": [],
""properties"": {
""optional"": { ""type"": ""string"" }
}
}");

var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings {
JsonLibrary = CSharpJsonLibrary.SystemTextJson,
RequiredPropertiesMustBeDefined = false
});

static string Normalized(string str) =>
Regex.Replace(str, @"\s+", " ");

//// Act
var code = generator.GenerateFile("MyClass");

/// Assert
Assert.Contains(
Normalized(@"
[System.Text.Json.Serialization.JsonPropertyName(""optional"")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
"),
Normalized(code)
);
}

[Fact]
public void When_array_property_is_required_or_not_then_the_code_has_correct_initializer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public async Task When_schema_has_base_schema_then_it_is_referenced_with_Newtons
Assert.True(json.Properties["Item"].ActualTypeSchema.AllOf.First().HasReference);
Assert.Contains("[Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), \"discriminator\")]", code);
Assert.Contains("[JsonInheritanceAttribute(\"Banana\", typeof(Banana))]", code);
Assert.Contains("internal class JsonInheritanceConverter : Newtonsoft.Json.JsonConverter", code);
Assert.Contains("public class JsonInheritanceConverter : Newtonsoft.Json.JsonConverter", code);
}

[Fact]
Expand All @@ -76,7 +76,7 @@ public async Task When_schema_has_base_schema_then_it_is_referenced_with_STJ()
Assert.True(json.Properties["Item"].ActualTypeSchema.AllOf.First().HasReference);
Assert.Contains("[JsonInheritanceConverter(typeof(Fruit), \"discriminator\")]", code);
Assert.Contains("[JsonInheritanceAttribute(\"Banana\", typeof(Banana))]", code);
Assert.Contains("internal class JsonInheritanceConverter<TBase> : System.Text.Json.Serialization.JsonConverter<TBase>", code);
Assert.Contains("public class JsonInheritanceConverter<TBase> : System.Text.Json.Serialization.JsonConverter<TBase>", code);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public virtual string Generate(JsonSchemaProperty property)
.Replace("*", "Star")
.Replace(":", "_")
.Replace("-", "_")
.Replace("#", "_");
.Replace("#", "_")
.Replace("&", "And");
}
}
}
8 changes: 4 additions & 4 deletions src/NJsonSchema.CodeGeneration.CSharp/CSharpValueGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ public override string GetDefaultValue(JsonSchema schema, bool allowsNull, strin
if (schema.Type.IsArray() ||
schema.Type.IsObject())
{
targetType = !string.IsNullOrEmpty(_settings.DictionaryInstanceType)
? targetType.Replace(_settings.DictionaryType + "<", _settings.DictionaryInstanceType + "<")
targetType = !string.IsNullOrEmpty(_settings.DictionaryInstanceType) && targetType.StartsWith(_settings.DictionaryType + "<")
? _settings.DictionaryInstanceType + targetType.Substring(_settings.DictionaryType.Length)
: targetType;

targetType = !string.IsNullOrEmpty(_settings.ArrayInstanceType)
? targetType.Replace(_settings.ArrayType + "<", _settings.ArrayInstanceType + "<")
targetType = !string.IsNullOrEmpty(_settings.ArrayInstanceType) && targetType.StartsWith(_settings.ArrayType + "<")
? _settings.ArrayInstanceType + targetType.Substring(_settings.ArrayType.Length)
: targetType;

return $"new {targetType}()";
Expand Down
14 changes: 10 additions & 4 deletions src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
// <copyright file="ClassTemplateModel.cs" company="NJsonSchema">
// Copyright (c) Rico Suter. All rights reserved.
// </copyright>
Expand Down Expand Up @@ -62,15 +62,21 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings,
/// <summary>Gets a value indicating whether the C#8 nullable reference types are enabled for this file.</summary>
public bool GenerateNullableReferenceTypes => _settings.GenerateNullableReferenceTypes;

/// <summary>Gets a value indicating whether an additional properties type is available and needed.</summary>
/// <summary>Gets a value indicating whether an additional properties type is available.</summary>
public bool HasAdditionalPropertiesType =>
HasAdditionalPropertiesTypeInBaseClass || // if the base class has them, inheritance dictates that this class will have them to
!_schema.IsDictionary &&
!_schema.ActualTypeSchema.IsDictionary &&
!_schema.IsArray &&
!_schema.ActualTypeSchema.IsArray &&
(_schema.ActualTypeSchema.AllowAdditionalProperties ||
_schema.ActualTypeSchema.AdditionalPropertiesSchema != null)
&& BaseClass?.HasAdditionalPropertiesType != true; // if base class already has extension data array, we need to avoid it in the subclass
_schema.ActualTypeSchema.AdditionalPropertiesSchema != null);

/// <summary>Gets a value indicating whether an additional properties type is available in the base class.</summary>
public bool HasAdditionalPropertiesTypeInBaseClass => BaseClass?.HasAdditionalPropertiesType ?? false;

/// <summary> Gets a value indicating if the "Additional properties" property should be generated. </summary>
public bool GenerateAdditionalPropertiesProperty => HasAdditionalPropertiesType && !HasAdditionalPropertiesTypeInBaseClass;

/// <summary>Gets the type of the additional properties.</summary>
public string AdditionalPropertiesType => HasAdditionalPropertiesType ? "object" : null; // TODO: Find a way to use typed dictionaries
Expand Down
12 changes: 11 additions & 1 deletion src/NJsonSchema.CodeGeneration.CSharp/Models/PropertyModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,17 @@ public PropertyModel(
(_property.ActualTypeSchema.IsArray && _settings.GenerateImmutableArrayProperties) ||
(_property.ActualTypeSchema.IsDictionary && _settings.GenerateImmutableDictionaryProperties)
)) == false;


/// <summary>Indicates whether or not this property has a <see cref="JsonIgnoreCondition"/>.</summary>
public bool HasJsonIgnoreCondition => JsonIgnoreCondition != null;

/// <summary>Returns the System.Text.Json.Serialization.JsonIgnoreCondition value to be applied to the property.</summary>
public string JsonIgnoreCondition => _property switch {
{ IsRequired: false } => "System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull",
{ IsRequired: true } when _settings.RequiredPropertiesMustBeDefined => "System.Text.Json.Serialization.JsonIgnoreCondition.Never",
_ => null
};

/// <summary>Gets the json property required.</summary>
public string JsonPropertyRequiredCode
{
Expand Down
Loading

0 comments on commit 71ce421

Please sign in to comment.