Skip to content

Commit

Permalink
Inline dictionary or array inheritance (#785)
Browse files Browse the repository at this point in the history
* Inline dictionary or array inheritance

* Fix c# generation
  • Loading branch information
RicoSuter committed Sep 26, 2018
1 parent 3a00302 commit 2464e2d
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 24 deletions.
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

0 comments on commit 2464e2d

Please sign in to comment.