Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline dictionary or array inheritance #785

Merged
merged 2 commits into from Sep 26, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/NJsonSchema.CodeGeneration.CSharp.Tests/EnumTests.cs
Expand Up @@ -226,5 +226,38 @@ public async Task When_type_name_hint_has_generics_then_they_are_converted()
/// Assert
Assert.Contains("public enum FirstMetdodOfMetValueGroupChar", code);
}

[Fact]
public async Task When_enum_property_is_not_required_in_Swagger2_then_it_is_nullable()
{
//// Arrange
var json =
@"{
""type"": ""object"",
""required"": [
""name"",
""photoUrls""
],
""properties"": {
""status"": {
""type"": ""string"",
""description"": ""pet status in the store"",
""enum"": [
""available"",
""pending"",
""sold""
]
}
}
}";
var schema = await JsonSchema4.FromJsonAsync(json);
var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings { SchemaType = SchemaType.Swagger2 });

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

//// Assert
Assert.Contains("private MyClassStatus? _status;", code);
}
}
}
41 changes: 41 additions & 0 deletions src/NJsonSchema.CodeGeneration.CSharp.Tests/InheritanceTests.cs
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NJsonSchema.CodeGeneration.CSharp;
using Xunit;

namespace NJsonSchema.CodeGeneration.Tests.CSharp
{
public class InheritanceTests
{
public class MyContainer
{
public EmptyClassInheritingDictionary CustomDictionary { get; set; }
}

public sealed class EmptyClassInheritingDictionary : Dictionary<string, object>
{
}

[Fact]
public async Task When_empty_class_inherits_from_dictionary_then_allOf_inheritance_still_works()
{
//// Arrange
var schema = await JsonSchema4.FromTypeAsync<MyContainer>();
var data = schema.ToJson();

var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings());

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

//// Assert
var dschema = schema.Definitions["EmptyClassInheritingDictionary"];
Assert.Equal(0, dschema.AllOf.Count);
Assert.True(dschema.IsDictionary);

Assert.Contains("public EmptyClassInheritingDictionary CustomDictionary", code);
Assert.Contains("public partial class EmptyClassInheritingDictionary : System.Collections.Generic.Dictionary<string, object>", code);
}
}
}
33 changes: 30 additions & 3 deletions src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs
Expand Up @@ -42,6 +42,17 @@ public CSharpTypeResolver(CSharpGeneratorSettings settings, JsonSchema4 exceptio
/// <param name="typeNameHint">The type name hint to use when generating the type and the type name is missing.</param>
/// <returns>The type name.</returns>
public override string Resolve(JsonSchema4 schema, bool isNullable, string typeNameHint)
{
return Resolve(schema, isNullable, typeNameHint, true);
}

/// <summary>Resolves and possibly generates the specified schema.</summary>
/// <param name="schema">The schema.</param>
/// <param name="isNullable">Specifies whether the given type usage is nullable.</param>
/// <param name="typeNameHint">The type name hint to use when generating the type and the type name is missing.</param>
/// <param name="checkForExistingSchema">Checks whether a named schema is already registered.</param>
/// <returns>The type name.</returns>
public string Resolve(JsonSchema4 schema, bool isNullable, string typeNameHint, bool checkForExistingSchema)
{
schema = schema.ActualSchema;

Expand All @@ -59,9 +70,6 @@ public override string Resolve(JsonSchema4 schema, bool isNullable, string typeN
JsonObjectType.String;
}

if (type.HasFlag(JsonObjectType.Array))
return ResolveArrayOrTuple(schema);

if (type.HasFlag(JsonObjectType.Number))
return ResolveNumber(schema, isNullable);

Expand All @@ -74,6 +82,12 @@ public override string Resolve(JsonSchema4 schema, bool isNullable, string typeN
if (type.HasFlag(JsonObjectType.String))
return ResolveString(schema, isNullable, typeNameHint);

if (Types.ContainsKey(schema) && checkForExistingSchema)
return Types[schema];

if (type.HasFlag(JsonObjectType.Array))
return ResolveArrayOrTuple(schema);

if (type.HasFlag(JsonObjectType.File))
return "byte[]";

Expand All @@ -83,6 +97,19 @@ public override string Resolve(JsonSchema4 schema, bool isNullable, string typeN
return GetOrGenerateTypeName(schema, typeNameHint);
}

/// <summary>Checks whether the given schema should generate a type.</summary>
/// <param name="schema">The schema.</param>
/// <returns>True if the schema should generate a type.</returns>
protected override bool IsTypeSchema(JsonSchema4 schema)
{
if (schema.IsDictionary || schema.IsArray)
{
return true;
}

return base.IsTypeSchema(schema);
}

private string ResolveString(JsonSchema4 schema, bool isNullable, string typeNameHint)
{
if (schema.Format == JsonFormatStrings.Date)
Expand Down
Expand Up @@ -39,7 +39,7 @@ public class ClassTemplateModel : ClassTemplateModelBase
.Select(property => new PropertyModel(this, property, _resolver, _settings))
.ToArray();

if (HasInheritance)
if (schema.InheritedSchema != null)
{
BaseClass = new ClassTemplateModel(BaseClassName, settings, resolver, schema.InheritedSchema, rootObject);
AllProperties = Properties.Concat(BaseClass.AllProperties).ToArray();
Expand Down Expand Up @@ -95,10 +95,10 @@ public class ClassTemplateModel : ClassTemplateModelBase
public string Discriminator => _schema.Discriminator;

/// <summary>Gets a value indicating whether the class has a parent class.</summary>
public bool HasInheritance => _schema.InheritedSchema != null;
public bool HasInheritance => _schema.InheritedTypeSchema != null;

/// <summary>Gets the base class name.</summary>
public string BaseClassName => HasInheritance ? _resolver.Resolve(_schema.InheritedSchema, false, string.Empty)
public string BaseClassName => HasInheritance ? _resolver.Resolve(_schema.InheritedTypeSchema, false, string.Empty, false)
.Replace(_settings.ArrayType + "<", _settings.ArrayBaseType + "<")
.Replace(_settings.DictionaryType + "<", _settings.DictionaryBaseType + "<") : null;

Expand Down
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;

namespace NJsonSchema.CodeGeneration.TypeScript.Tests
{
public class InheritanceTests
{
public class MyContainer
{
public EmptyClassInheritingDictionary CustomDictionary { get; set; }
}

public sealed class EmptyClassInheritingDictionary : Dictionary<string, object>
{
}

[Fact]
public async Task When_empty_class_inherits_from_dictionary_then_allOf_inheritance_still_works()
{
//// Arrange
var schema = await JsonSchema4.FromTypeAsync<MyContainer>();
var data = schema.ToJson();

var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings { TypeScriptVersion = 2.0m });

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

//// Assert
var dschema = schema.Definitions["EmptyClassInheritingDictionary"];
Assert.Equal(0, dschema.AllOf.Count);
Assert.True(dschema.IsDictionary);

Assert.DoesNotContain("EmptyClassInheritingDictionary", code);
Assert.Contains("customDictionary: { [key: string] : any; } | undefined;", code);
}
}
}
7 changes: 7 additions & 0 deletions src/NJsonSchema.CodeGeneration/CodeArtifact.cs
Expand Up @@ -6,6 +6,8 @@
// <author>Rico Suter, mail@rsuter.com</author>
//-----------------------------------------------------------------------

using System;

namespace NJsonSchema.CodeGeneration
{
/// <summary>The type generator result.</summary>
Expand Down Expand Up @@ -49,6 +51,11 @@ public CodeArtifact(string typeName, CodeArtifactType type, CodeArtifactLanguage
/// <param name="template">The template to render the code.</param>
public CodeArtifact(string typeName, string baseTypeName, CodeArtifactType type, CodeArtifactLanguage language, ITemplate template)
{
if (typeName == baseTypeName)
{
throw new ArgumentException("The baseTypeName cannot equal typeName.", nameof(typeName));
}

TypeName = typeName;
BaseTypeName = baseTypeName;

Expand Down
18 changes: 15 additions & 3 deletions src/NJsonSchema.CodeGeneration/TypeResolverBase.cs
Expand Up @@ -70,15 +70,27 @@ public void RegisterSchemaDefinitions(IDictionary<string, JsonSchema4> definitio
foreach (var pair in definitions)
{
var schema = pair.Value.ActualSchema;
var isCodeGeneratingSchema = !schema.IsDictionary && !schema.IsAnyType &&
(schema.IsEnumeration || schema.Type == JsonObjectType.None || schema.Type.HasFlag(JsonObjectType.Object));

if (isCodeGeneratingSchema)
if (IsTypeSchema(schema))
{
GetOrGenerateTypeName(schema, pair.Key);
}
}
}
}

/// <summary>Checks whether the given schema should generate a type.</summary>
/// <param name="schema">The schema.</param>
/// <returns>True if the schema should generate a type.</returns>
protected virtual bool IsTypeSchema(JsonSchema4 schema)
{
return !schema.IsDictionary &&
!schema.IsAnyType &&
(schema.IsEnumeration ||
schema.Type == JsonObjectType.None ||
schema.Type.HasFlag(JsonObjectType.Object));
}

/// <summary>Resolves the type of the dictionary value of the given schema (must be a dictionary schema).</summary>
/// <param name="schema">The schema.</param>
/// <param name="fallbackType">The fallback type (e.g. 'object').</param>
Expand Down
42 changes: 31 additions & 11 deletions src/NJsonSchema/Generation/JsonSchemaGenerator.cs
Expand Up @@ -700,26 +700,46 @@ private async Task<JsonSchema4> GenerateInheritanceAsync(Type type, JsonSchema4
else
{
var actualSchema = new JsonSchema4();

await GeneratePropertiesAsync(type, actualSchema, schemaResolver).ConfigureAwait(false);
await ApplyAdditionalPropertiesAsync(type, actualSchema, schemaResolver).ConfigureAwait(false);

var baseSchema = await GenerateAsync(baseType, schemaResolver).ConfigureAwait(false);
var baseTypeInfo = Settings.ReflectionService.GetDescription(baseType, null, Settings);
if (baseTypeInfo.RequiresSchemaReference(Settings.TypeMappers))
var requiresSchemaReference = baseTypeInfo.RequiresSchemaReference(Settings.TypeMappers);

if (actualSchema.Properties.Any() || requiresSchemaReference)
{
if (schemaResolver.RootObject != baseSchema.ActualSchema)
schemaResolver.AppendSchema(baseSchema.ActualSchema, Settings.SchemaNameGenerator.Generate(baseType));
// Use allOf inheritance only if the schema is an object with properties
// (not empty class which just inherits from array or dictionary)

schema.AllOf.Add(new JsonSchema4
var baseSchema = await GenerateAsync(baseType, schemaResolver).ConfigureAwait(false);
if (requiresSchemaReference)
{
if (schemaResolver.RootObject != baseSchema.ActualSchema)
{
schemaResolver.AppendSchema(baseSchema.ActualSchema, Settings.SchemaNameGenerator.Generate(baseType));
}

schema.AllOf.Add(new JsonSchema4
{
Reference = baseSchema.ActualSchema
});
}
else
{
Reference = baseSchema.ActualSchema
});
schema.AllOf.Add(baseSchema);
}

// First schema is the (referenced) base schema, second is the type schema itself
schema.AllOf.Add(actualSchema);
return actualSchema;
}
else
schema.AllOf.Add(baseSchema);

schema.AllOf.Add(actualSchema);
return actualSchema;
{
// Array and dictionary inheritance are not expressed with allOf but inline
await GenerateAsync(baseType, null, schema, schemaResolver).ConfigureAwait(false);
return schema;
}
}
}
}
Expand Down
19 changes: 15 additions & 4 deletions src/NJsonSchema/JsonSchema4.cs
Expand Up @@ -205,11 +205,7 @@ internal static JsonSchema4 FromJsonWithoutReferenceHandling(string data)
/// <summary>Gets the inherited/parent schema (most probable base schema in allOf).</summary>
/// <remarks>Used for code generation.</remarks>
[JsonIgnore]
#if !LEGACY
public JsonSchema4 InheritedSchema
#else
public JsonSchema4 InheritedSchema
#endif
{
get
{
Expand All @@ -229,6 +225,21 @@ public JsonSchema4 InheritedSchema
}
}

/// <summary>Gets the inherited/parent schema which may also be inlined
/// (the schema itself if it is a dictionary or array, otherwise <see cref="InheritedSchema"/>).</summary>
/// <remarks>Used for code generation.</remarks>
[JsonIgnore]
public JsonSchema4 InheritedTypeSchema
{
get
{
if (ActualTypeSchema.IsDictionary || ActualTypeSchema.IsArray)
return ActualTypeSchema;

return InheritedSchema;
}
}

/// <summary>Gets the list of all inherited/parent schemas.</summary>
/// <remarks>Used for code generation.</remarks>
[JsonIgnore]
Expand Down