forked from loafle/openapi-generator-original
[csharp] Support inheritance instead of duplicating parent properties in derived classes (#5922)
* [csharp] Explicitly set supportsInheritance * [csharp] set supportsInheritance for client This includes supportsInheritance only for the client codegen at the moment, because setting in AbstractCSharpCodegen would require the change to be tested in all derived generators, possibly including similar template changes to this commit's. * include nice improvement of https://github.com/jimschubert/swagger-codegen/tree/csharp/3829 and leverage https://github.com/manuc66/JsonSubTypes for subtype deserialization * remove duplicate base validations * remove useless tests * restore documentation for properties coming from parent * launch bin/security/csharp-petstore.sh * it's impossible to call an explicitly implemented interface-method on the base class (https://stackoverflow.com/questions/5976216/how-to-call-an-explicitly-implemented-interface-method-on-the-base-class) * restore portion of code that was lost * regenerate more * fix missing using * take the multi .net compatible revision * keep generated model simple when no hierarchy involved * regenerate with: - bin/csharp-petstore-all.sh && bin/security/csharp-petstore.sh - bin/csharp-dotnet2-petstore.sh && bin/csharp-petstore.sh && bin/csharp-petstore-netcore-project.sh && bin/csharp-petstore-net-standard.sh && bin/csharp-property-changed-petstore.sh * fix sln indentation and the missing windows runner for dotnet2 * fix inheritance GetHashCode and Equals * override instead of hiding the base method + fix the csharp-property-changed-petstore.bat * By default the value of the discriminator property must be the name of the current schema * Add test for subtype deserialisation from parent type * add missing '.bat' and use the 'call' template from javascript-petstore-all.bat add missing file to trigger it on windows * fix default value bug * cleanup copyright information * formatting after merge * fix merge * applying bin/csharp-petstore-all.sh * applying bin/security/csharp-petstore.sh
This commit is contained in:
@@ -3159,6 +3159,7 @@ public class DefaultCodegen {
|
||||
if (Boolean.TRUE.equals(cp.isReadOnly)) {
|
||||
m.readOnlyVars.add(cp);
|
||||
} else { // else add to readWriteVars (list of properties)
|
||||
// FIXME: readWriteVars can contain duplicated properties. Debug/breakpoint here while running C# generator (Dog and Cat models)
|
||||
m.readWriteVars.add(cp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
package io.swagger.codegen.languages;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import com.sun.org.apache.bcel.internal.classfile.Code;
|
||||
|
||||
import io.swagger.codegen.CodegenConstants;
|
||||
import io.swagger.codegen.CodegenType;
|
||||
import io.swagger.codegen.CodegenModel;
|
||||
import io.swagger.codegen.CodegenParameter;
|
||||
import io.swagger.codegen.SupportingFile;
|
||||
import io.swagger.codegen.CodegenProperty;
|
||||
import io.swagger.codegen.CodegenOperation;
|
||||
import io.swagger.codegen.CliOption;
|
||||
import io.swagger.codegen.*;
|
||||
import io.swagger.models.Model;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isEmpty;
|
||||
|
||||
public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
@SuppressWarnings({"unused", "hiding"})
|
||||
@SuppressWarnings({"hiding"})
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CSharpClientCodegen.class);
|
||||
private static final String NET45 = "v4.5";
|
||||
private static final String NET35 = "v3.5";
|
||||
private static final String NETSTANDARD = "v5.0";
|
||||
private static final String UWP = "uwp";
|
||||
private static final String DATA_TYPE_WITH_ENUM_EXTENSION = "plainDatatypeWithEnum";
|
||||
|
||||
protected String packageGuid = "{" + java.util.UUID.randomUUID().toString().toUpperCase() + "}";
|
||||
protected String clientPackage = "IO.Swagger.Client";
|
||||
@@ -55,6 +39,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
|
||||
public CSharpClientCodegen() {
|
||||
super();
|
||||
supportsInheritance = true;
|
||||
modelTemplateFiles.put("model.mustache", ".cs");
|
||||
apiTemplateFiles.put("api.mustache", ".cs");
|
||||
|
||||
@@ -181,16 +166,16 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString()));
|
||||
}
|
||||
|
||||
if(isEmpty(apiPackage)) {
|
||||
if (isEmpty(apiPackage)) {
|
||||
apiPackage = "Api";
|
||||
}
|
||||
if(isEmpty(modelPackage)) {
|
||||
if (isEmpty(modelPackage)) {
|
||||
modelPackage = "Model";
|
||||
}
|
||||
clientPackage = "Client";
|
||||
|
||||
Boolean excludeTests = false;
|
||||
if(additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) {
|
||||
if (additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) {
|
||||
excludeTests = Boolean.valueOf(additionalProperties.get(CodegenConstants.EXCLUDE_TESTS).toString());
|
||||
}
|
||||
|
||||
@@ -215,12 +200,12 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
if (NET35.equals(this.targetFramework)) {
|
||||
setTargetFrameworkNuget("net35");
|
||||
setSupportsAsync(Boolean.FALSE);
|
||||
if(additionalProperties.containsKey("supportsAsync")){
|
||||
if (additionalProperties.containsKey("supportsAsync")) {
|
||||
additionalProperties.remove("supportsAsync");
|
||||
}
|
||||
additionalProperties.put("validatable", false);
|
||||
additionalProperties.put("net35", true);
|
||||
} else if (NETSTANDARD.equals(this.targetFramework)){
|
||||
} else if (NETSTANDARD.equals(this.targetFramework)) {
|
||||
setTargetFrameworkNuget("netstandard1.3");
|
||||
setSupportsAsync(Boolean.TRUE);
|
||||
setSupportsUWP(Boolean.FALSE);
|
||||
@@ -232,11 +217,11 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
//Tests not yet implemented for .NET Standard codegen
|
||||
//Todo implement it
|
||||
excludeTests = true;
|
||||
if(additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)){
|
||||
if (additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) {
|
||||
additionalProperties.remove(CodegenConstants.EXCLUDE_TESTS);
|
||||
}
|
||||
additionalProperties.put(CodegenConstants.EXCLUDE_TESTS, excludeTests);
|
||||
} else if (UWP.equals(this.targetFramework)){
|
||||
} else if (UWP.equals(this.targetFramework)) {
|
||||
setTargetFrameworkNuget("uwp");
|
||||
setSupportsAsync(Boolean.TRUE);
|
||||
setSupportsUWP(Boolean.TRUE);
|
||||
@@ -249,18 +234,18 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
additionalProperties.put("supportsAsync", this.supportsAsync);
|
||||
}
|
||||
|
||||
if(additionalProperties.containsKey(CodegenConstants.GENERATE_PROPERTY_CHANGED)) {
|
||||
if(NET35.equals(targetFramework)) {
|
||||
if (additionalProperties.containsKey(CodegenConstants.GENERATE_PROPERTY_CHANGED)) {
|
||||
if (NET35.equals(targetFramework)) {
|
||||
LOGGER.warn(CodegenConstants.GENERATE_PROPERTY_CHANGED + " is only supported by generated code for .NET 4+.");
|
||||
} else if(NETSTANDARD.equals(targetFramework)) {
|
||||
} else if (NETSTANDARD.equals(targetFramework)) {
|
||||
LOGGER.warn(CodegenConstants.GENERATE_PROPERTY_CHANGED + " is not supported in .NET Standard generated code.");
|
||||
} else if(Boolean.TRUE.equals(netCoreProjectFileFlag)) {
|
||||
} else if (Boolean.TRUE.equals(netCoreProjectFileFlag)) {
|
||||
LOGGER.warn(CodegenConstants.GENERATE_PROPERTY_CHANGED + " is not supported in .NET Core csproj project format.");
|
||||
} else {
|
||||
setGeneratePropertyChanged(Boolean.valueOf(additionalProperties.get(CodegenConstants.GENERATE_PROPERTY_CHANGED).toString()));
|
||||
}
|
||||
|
||||
if(Boolean.FALSE.equals(this.generatePropertyChanged)) {
|
||||
if (Boolean.FALSE.equals(this.generatePropertyChanged)) {
|
||||
additionalProperties.remove(CodegenConstants.GENERATE_PROPERTY_CHANGED);
|
||||
}
|
||||
}
|
||||
@@ -322,8 +307,10 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
clientPackageDir, "ExceptionFactory.cs"));
|
||||
supportingFiles.add(new SupportingFile("SwaggerDateConverter.mustache",
|
||||
clientPackageDir, "SwaggerDateConverter.cs"));
|
||||
supportingFiles.add(new SupportingFile("JsonSubTypes.mustache",
|
||||
clientPackageDir, "JsonSubTypes.cs"));
|
||||
|
||||
if(Boolean.FALSE.equals(this.netStandard) && Boolean.FALSE.equals(this.netCoreProjectFileFlag)) {
|
||||
if (Boolean.FALSE.equals(this.netStandard) && Boolean.FALSE.equals(this.netCoreProjectFileFlag)) {
|
||||
supportingFiles.add(new SupportingFile("compile.mustache", "", "build.bat"));
|
||||
supportingFiles.add(new SupportingFile("compile-mono.sh.mustache", "", "build.sh"));
|
||||
|
||||
@@ -331,7 +318,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
supportingFiles.add(new SupportingFile("packages.config.mustache", packageFolder + File.separator, "packages.config"));
|
||||
// .travis.yml for travis-ci.org CI
|
||||
supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml"));
|
||||
} else if(Boolean.FALSE.equals(this.netCoreProjectFileFlag)) {
|
||||
} else if (Boolean.FALSE.equals(this.netCoreProjectFileFlag)) {
|
||||
supportingFiles.add(new SupportingFile("project.json.mustache", packageFolder + File.separator, "project.json"));
|
||||
}
|
||||
|
||||
@@ -341,7 +328,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
clientPackageDir, "GlobalConfiguration.cs"));
|
||||
|
||||
// Only write out test related files if excludeTests is unset or explicitly set to false (see start of this method)
|
||||
if(Boolean.FALSE.equals(excludeTests)) {
|
||||
if (Boolean.FALSE.equals(excludeTests)) {
|
||||
// shell script to run the nunit test
|
||||
supportingFiles.add(new SupportingFile("mono_nunit_test.mustache", "", "mono_nunit_test.sh"));
|
||||
|
||||
@@ -353,7 +340,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
}
|
||||
}
|
||||
|
||||
if(Boolean.TRUE.equals(generatePropertyChanged)) {
|
||||
if (Boolean.TRUE.equals(generatePropertyChanged)) {
|
||||
supportingFiles.add(new SupportingFile("FodyWeavers.xml", packageFolder, "FodyWeavers.xml"));
|
||||
}
|
||||
|
||||
@@ -369,19 +356,19 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
}
|
||||
if (optionalProjectFileFlag) {
|
||||
supportingFiles.add(new SupportingFile("Solution.mustache", "", packageName + ".sln"));
|
||||
|
||||
if(Boolean.TRUE.equals(this.netCoreProjectFileFlag)) {
|
||||
|
||||
if (Boolean.TRUE.equals(this.netCoreProjectFileFlag)) {
|
||||
supportingFiles.add(new SupportingFile("netcore_project.mustache", packageFolder, packageName + ".csproj"));
|
||||
} else {
|
||||
supportingFiles.add(new SupportingFile("Project.mustache", packageFolder, packageName + ".csproj"));
|
||||
if(Boolean.FALSE.equals(this.netStandard)) {
|
||||
if (Boolean.FALSE.equals(this.netStandard)) {
|
||||
supportingFiles.add(new SupportingFile("nuspec.mustache", packageFolder, packageName + ".nuspec"));
|
||||
}
|
||||
}
|
||||
|
||||
if(Boolean.FALSE.equals(excludeTests)) {
|
||||
if (Boolean.FALSE.equals(excludeTests)) {
|
||||
// NOTE: This exists here rather than previous excludeTests block because the test project is considered an optional project file.
|
||||
if(Boolean.TRUE.equals(this.netCoreProjectFileFlag)) {
|
||||
if (Boolean.TRUE.equals(this.netCoreProjectFileFlag)) {
|
||||
supportingFiles.add(new SupportingFile("netcore_testproject.mustache", testPackageFolder, testPackageName + ".csproj"));
|
||||
} else {
|
||||
supportingFiles.add(new SupportingFile("TestProject.mustache", testPackageFolder, testPackageName + ".csproj"));
|
||||
@@ -457,10 +444,54 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
@Override
|
||||
public CodegenModel fromModel(String name, Model model, Map<String, Model> allDefinitions) {
|
||||
CodegenModel codegenModel = super.fromModel(name, model, allDefinitions);
|
||||
if (allDefinitions != null && codegenModel != null && codegenModel.parent != null && codegenModel.hasEnums) {
|
||||
if (allDefinitions != null && codegenModel != null && codegenModel.parent != null) {
|
||||
final Model parentModel = allDefinitions.get(toModelName(codegenModel.parent));
|
||||
final CodegenModel parentCodegenModel = super.fromModel(codegenModel.parent, parentModel);
|
||||
codegenModel = this.reconcileInlineEnums(codegenModel, parentCodegenModel);
|
||||
if (parentModel != null) {
|
||||
final CodegenModel parentCodegenModel = super.fromModel(codegenModel.parent, parentModel);
|
||||
if (codegenModel.hasEnums) {
|
||||
codegenModel = this.reconcileInlineEnums(codegenModel, parentCodegenModel);
|
||||
}
|
||||
|
||||
Map<String, CodegenProperty> propertyHash = new HashMap<>(codegenModel.vars.size());
|
||||
for (final CodegenProperty property : codegenModel.vars) {
|
||||
propertyHash.put(property.name, property);
|
||||
}
|
||||
|
||||
for (final CodegenProperty property : codegenModel.readWriteVars) {
|
||||
if (property.defaultValue == null && property.baseName.equals(parentCodegenModel.discriminator)) {
|
||||
property.defaultValue = "\"" + name + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
CodegenProperty last = null;
|
||||
for (final CodegenProperty property : parentCodegenModel.vars) {
|
||||
// helper list of parentVars simplifies templating
|
||||
if (!propertyHash.containsKey(property.name)) {
|
||||
final CodegenProperty parentVar = property.clone();
|
||||
parentVar.isInherited = true;
|
||||
parentVar.hasMore = true;
|
||||
last = parentVar;
|
||||
LOGGER.info("adding parent variable {}", property.name);
|
||||
codegenModel.parentVars.add(parentVar);
|
||||
}
|
||||
}
|
||||
|
||||
if (last != null) {
|
||||
last.hasMore = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup possible duplicates. Currently, readWriteVars can contain the same property twice. May or may not be isolated to C#.
|
||||
if (codegenModel != null && codegenModel.readWriteVars != null && codegenModel.readWriteVars.size() > 1) {
|
||||
int length = codegenModel.readWriteVars.size() - 1;
|
||||
for (int i = length; i > (length / 2); i--) {
|
||||
final CodegenProperty codegenProperty = codegenModel.readWriteVars.get(i);
|
||||
// If the property at current index is found earlier in the list, remove this last instance.
|
||||
if (codegenModel.readWriteVars.indexOf(codegenProperty) < i) {
|
||||
codegenModel.readWriteVars.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return codegenModel;
|
||||
@@ -500,13 +531,13 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
* See https://github.com/swagger-api/swagger-codegen/pull/2794 for Python's initial implementation from which this is copied.
|
||||
*/
|
||||
public void postProcessPattern(String pattern, Map<String, Object> vendorExtensions) {
|
||||
if(pattern != null) {
|
||||
if (pattern != null) {
|
||||
int i = pattern.lastIndexOf('/');
|
||||
|
||||
//Must follow Perl /pattern/modifiers convention
|
||||
if(pattern.charAt(0) != '/' || i < 2) {
|
||||
if (pattern.charAt(0) != '/' || i < 2) {
|
||||
throw new IllegalArgumentException("Pattern must follow the Perl "
|
||||
+ "/pattern/modifiers convention. "+pattern+" is not valid.");
|
||||
+ "/pattern/modifiers convention. " + pattern + " is not valid.");
|
||||
}
|
||||
|
||||
String regex = pattern.substring(1, i).replace("'", "\'");
|
||||
@@ -515,8 +546,8 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
// perl requires an explicit modifier to be culture specific and .NET is the reverse.
|
||||
modifiers.add("CultureInvariant");
|
||||
|
||||
for(char c : pattern.substring(i).toCharArray()) {
|
||||
if(regexModifiers.containsKey(c)) {
|
||||
for (char c : pattern.substring(i).toCharArray()) {
|
||||
if (regexModifiers.containsKey(c)) {
|
||||
String modifier = regexModifiers.get(c);
|
||||
modifiers.add(modifier);
|
||||
} else if (c == 'l') {
|
||||
@@ -530,7 +561,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
}
|
||||
|
||||
public void setTargetFramework(String dotnetFramework) {
|
||||
if(!frameworks.containsKey(dotnetFramework)){
|
||||
if (!frameworks.containsKey(dotnetFramework)) {
|
||||
LOGGER.warn("Invalid .NET framework version, defaulting to " + this.targetFramework);
|
||||
} else {
|
||||
this.targetFramework = dotnetFramework;
|
||||
@@ -572,10 +603,10 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
}
|
||||
}
|
||||
|
||||
if(removedChildEnum) {
|
||||
if (removedChildEnum) {
|
||||
// If we removed an entry from this model's vars, we need to ensure hasMore is updated
|
||||
int count = 0, numVars = codegenProperties.size();
|
||||
for(CodegenProperty codegenProperty : codegenProperties) {
|
||||
for (CodegenProperty codegenProperty : codegenProperties) {
|
||||
count += 1;
|
||||
codegenProperty.hasMore = count < numVars;
|
||||
}
|
||||
@@ -589,7 +620,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
@Override
|
||||
public String toEnumValue(String value, String datatype) {
|
||||
if ("int?".equalsIgnoreCase(datatype) || "long?".equalsIgnoreCase(datatype) ||
|
||||
"double?".equalsIgnoreCase(datatype) || "float?".equalsIgnoreCase(datatype)) {
|
||||
"double?".equalsIgnoreCase(datatype) || "float?".equalsIgnoreCase(datatype)) {
|
||||
return value;
|
||||
} else if ("float?".equalsIgnoreCase(datatype)) {
|
||||
// for float in C#, append "f". e.g. 3.14 => 3.14f
|
||||
@@ -611,8 +642,8 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
}
|
||||
|
||||
// number
|
||||
if ("int?".equals(datatype) || "long?".equals(datatype) ||
|
||||
"double?".equals(datatype) || "float?".equals(datatype)) {
|
||||
if ("int?".equals(datatype) || "long?".equals(datatype) ||
|
||||
"double?".equals(datatype) || "float?".equals(datatype)) {
|
||||
String varName = "NUMBER_" + value;
|
||||
varName = varName.replaceAll("-", "MINUS_");
|
||||
varName = varName.replaceAll("\\+", "PLUS_");
|
||||
@@ -678,19 +709,19 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
|
||||
this.targetFrameworkNuget = targetFrameworkNuget;
|
||||
}
|
||||
|
||||
public void setSupportsAsync(Boolean supportsAsync){
|
||||
public void setSupportsAsync(Boolean supportsAsync) {
|
||||
this.supportsAsync = supportsAsync;
|
||||
}
|
||||
|
||||
public void setSupportsUWP(Boolean supportsUWP){
|
||||
public void setSupportsUWP(Boolean supportsUWP) {
|
||||
this.supportsUWP = supportsUWP;
|
||||
}
|
||||
|
||||
public void setNetStandard(Boolean netStandard){
|
||||
public void setNetStandard(Boolean netStandard) {
|
||||
this.netStandard = netStandard;
|
||||
}
|
||||
|
||||
public void setGeneratePropertyChanged(final Boolean generatePropertyChanged){
|
||||
public void setGeneratePropertyChanged(final Boolean generatePropertyChanged) {
|
||||
this.generatePropertyChanged = generatePropertyChanged;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace JsonSubTypes
|
||||
{
|
||||
// Copied from project https://github.com/manuc66/JsonSubTypes
|
||||
// https://raw.githubusercontent.com/manuc66/JsonSubTypes/07403192ea3f4959f6d42f5966ac56ceb0d6095b/JsonSubTypes/JsonSubtypes.cs
|
||||
|
||||
public class JsonSubtypes : JsonConverter
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)]
|
||||
public class KnownSubTypeAttribute : Attribute
|
||||
{
|
||||
public Type SubType { get; private set; }
|
||||
public object AssociatedValue { get; private set; }
|
||||
|
||||
public KnownSubTypeAttribute(Type subType, object associatedValue)
|
||||
{
|
||||
SubType = subType;
|
||||
AssociatedValue = associatedValue;
|
||||
}
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)]
|
||||
public class KnownSubTypeWithPropertyAttribute : Attribute
|
||||
{
|
||||
public Type SubType { get; private set; }
|
||||
public string PropertyName { get; private set; }
|
||||
|
||||
public KnownSubTypeWithPropertyAttribute(Type subType, string propertyName)
|
||||
{
|
||||
SubType = subType;
|
||||
PropertyName = propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly string _typeMappingPropertyName;
|
||||
|
||||
private bool _isInsideRead;
|
||||
private JsonReader _reader;
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isInsideRead)
|
||||
return true;
|
||||
|
||||
return !string.IsNullOrEmpty(_reader.Path);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed override bool CanWrite
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public JsonSubtypes()
|
||||
{
|
||||
}
|
||||
|
||||
public JsonSubtypes(string typeMappingPropertyName)
|
||||
{
|
||||
_typeMappingPropertyName = typeMappingPropertyName;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return _typeMappingPropertyName != null;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Comment)
|
||||
reader.Read();
|
||||
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.Null:
|
||||
return null;
|
||||
case JsonToken.StartArray:
|
||||
return ReadArray(reader, objectType, serializer);
|
||||
case JsonToken.StartObject:
|
||||
return ReadObject(reader, objectType, serializer);
|
||||
default:
|
||||
throw new Exception("Array: Unrecognized token: " + reader.TokenType);
|
||||
}
|
||||
}
|
||||
|
||||
private IList ReadArray(JsonReader reader, Type targetType, JsonSerializer serializer)
|
||||
{
|
||||
var elementType = GetElementType(targetType);
|
||||
|
||||
var list = CreateCompatibleList(targetType, elementType);
|
||||
|
||||
while (reader.TokenType != JsonToken.EndArray && reader.Read())
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.Null:
|
||||
list.Add(reader.Value);
|
||||
break;
|
||||
case JsonToken.Comment:
|
||||
break;
|
||||
case JsonToken.StartObject:
|
||||
list.Add(ReadObject(reader, elementType, serializer));
|
||||
break;
|
||||
case JsonToken.EndArray:
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Array: Unrecognized token: " + reader.TokenType);
|
||||
}
|
||||
}
|
||||
if (targetType.IsArray)
|
||||
{
|
||||
var array = Array.CreateInstance(targetType.GetElementType(), list.Count);
|
||||
list.CopyTo(array, 0);
|
||||
list = array;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static IList CreateCompatibleList(Type targetContainerType, Type elementType)
|
||||
{
|
||||
IList list;
|
||||
if (targetContainerType.IsArray || targetContainerType.GetTypeInfo().IsAbstract)
|
||||
{
|
||||
list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
|
||||
}
|
||||
else
|
||||
{
|
||||
list = (IList)Activator.CreateInstance(targetContainerType);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static Type GetElementType(Type arrayOrGenericContainer)
|
||||
{
|
||||
Type elementType;
|
||||
if (arrayOrGenericContainer.IsArray)
|
||||
{
|
||||
elementType = arrayOrGenericContainer.GetElementType();
|
||||
}
|
||||
else
|
||||
{
|
||||
elementType = arrayOrGenericContainer.GenericTypeArguments[0];
|
||||
}
|
||||
return elementType;
|
||||
}
|
||||
|
||||
private object ReadObject(JsonReader reader, Type objectType, JsonSerializer serializer)
|
||||
{
|
||||
var jObject = JObject.Load(reader);
|
||||
|
||||
var targetType = GetType(jObject, objectType) ?? objectType;
|
||||
|
||||
return _ReadJson(CreateAnotherReader(jObject, reader), targetType, null, serializer);
|
||||
}
|
||||
|
||||
private static JsonReader CreateAnotherReader(JObject jObject, JsonReader reader)
|
||||
{
|
||||
var jObjectReader = jObject.CreateReader();
|
||||
jObjectReader.Culture = reader.Culture;
|
||||
jObjectReader.CloseInput = reader.CloseInput;
|
||||
jObjectReader.SupportMultipleContent = reader.SupportMultipleContent;
|
||||
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
|
||||
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
|
||||
jObjectReader.DateFormatString = reader.DateFormatString;
|
||||
jObjectReader.DateParseHandling = reader.DateParseHandling;
|
||||
return jObjectReader;
|
||||
}
|
||||
|
||||
public Type GetType(JObject jObject, Type parentType)
|
||||
{
|
||||
if (_typeMappingPropertyName == null)
|
||||
{
|
||||
return GetTypeByPropertyPresence(jObject, parentType);
|
||||
}
|
||||
return GetTypeFromDiscriminatorValue(jObject, parentType);
|
||||
}
|
||||
|
||||
private static Type GetTypeByPropertyPresence(JObject jObject, Type parentType)
|
||||
{
|
||||
foreach (var type in parentType.GetTypeInfo().GetCustomAttributes<KnownSubTypeWithPropertyAttribute>())
|
||||
{
|
||||
JToken ignore;
|
||||
if (jObject.TryGetValue(type.PropertyName, out ignore))
|
||||
{
|
||||
return type.SubType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Type GetTypeFromDiscriminatorValue(JObject jObject, Type parentType)
|
||||
{
|
||||
JToken jToken;
|
||||
if (!jObject.TryGetValue(_typeMappingPropertyName, out jToken)) return null;
|
||||
|
||||
var discriminatorValue = jToken.ToObject<object>();
|
||||
if (discriminatorValue == null) return null;
|
||||
|
||||
var typeMapping = GetSubTypeMapping(parentType);
|
||||
if (typeMapping.Any())
|
||||
{
|
||||
return GetTypeFromMapping(typeMapping, discriminatorValue);
|
||||
}
|
||||
return GetTypeByName(discriminatorValue as string, parentType);
|
||||
}
|
||||
|
||||
private static Type GetTypeByName(string typeName, Type parentType)
|
||||
{
|
||||
if (typeName == null)
|
||||
return null;
|
||||
|
||||
var insideAssembly = parentType.GetTypeInfo().Assembly;
|
||||
|
||||
var typeByName = insideAssembly.GetType(typeName);
|
||||
if (typeByName == null)
|
||||
{
|
||||
var searchLocation = parentType.FullName.Substring(0, parentType.FullName.Length - parentType.Name.Length);
|
||||
typeByName = insideAssembly.GetType(searchLocation + typeName, false, true);
|
||||
}
|
||||
return typeByName;
|
||||
}
|
||||
|
||||
private static Type GetTypeFromMapping(IReadOnlyDictionary<object, Type> typeMapping, object discriminatorValue)
|
||||
{
|
||||
var targetlookupValueType = typeMapping.First().Key.GetType();
|
||||
var lookupValue = ConvertJsonValueToType(discriminatorValue, targetlookupValueType);
|
||||
|
||||
Type targetType;
|
||||
return typeMapping.TryGetValue(lookupValue, out targetType) ? targetType : null;
|
||||
}
|
||||
|
||||
private static Dictionary<object, Type> GetSubTypeMapping(Type type)
|
||||
{
|
||||
return type.GetTypeInfo().GetCustomAttributes<KnownSubTypeAttribute>().ToDictionary(x => x.AssociatedValue, x => x.SubType);
|
||||
}
|
||||
|
||||
private static object ConvertJsonValueToType(object objectType, Type targetlookupValueType)
|
||||
{
|
||||
if (targetlookupValueType.GetTypeInfo().IsEnum)
|
||||
return Enum.ToObject(targetlookupValueType, objectType);
|
||||
|
||||
return Convert.ChangeType(objectType, targetlookupValueType);
|
||||
}
|
||||
|
||||
protected object _ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
_reader = reader;
|
||||
_isInsideRead = true;
|
||||
try
|
||||
{
|
||||
return serializer.Deserialize(reader, objectType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isInsideRead = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,21 @@ EndProject
|
||||
{{^excludeTests}}Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{testPackageName}}", "src\{{testPackageName}}\{{testPackageName}}.csproj", "{19F1DEBC-DE5E-4517-8062-F000CD499087}"
|
||||
EndProject
|
||||
{{/excludeTests}}Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{{packageGuid}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{{packageGuid}}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{{packageGuid}}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{{packageGuid}}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{{packageGuid}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{{packageGuid}}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{{packageGuid}}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{{packageGuid}}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -12,6 +12,13 @@ using System.Collections.ObjectModel;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
{{#discriminator}}
|
||||
using JsonSubTypes;
|
||||
{{/discriminator}}
|
||||
{{/model}}
|
||||
{{/models}}
|
||||
{{^netStandard}}
|
||||
{{#generatePropertyChanged}}
|
||||
using PropertyChanged;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
/// {{#description}}{{.}}{{/description}}{{^description}}{{classname}}{{/description}}
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
{{#discriminator}}
|
||||
[JsonConverter(typeof(JsonSubtypes), "{{discriminator}}")]{{#children}}
|
||||
[JsonSubtypes.KnownSubType(typeof({{classname}}), "{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}")]{{/children}}
|
||||
{{/discriminator}}
|
||||
{{#generatePropertyChanged}}
|
||||
[ImplementPropertyChanged]
|
||||
{{/generatePropertyChanged}}
|
||||
@@ -49,9 +53,10 @@
|
||||
{{#hasOnlyReadOnly}}
|
||||
[JsonConstructorAttribute]
|
||||
{{/hasOnlyReadOnly}}
|
||||
public {{classname}}({{#readWriteVars}}{{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}?{{/isContainer}}{{/isEnum}} {{name}} = {{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}default({{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}?{{/isContainer}}{{/isEnum}}){{/defaultValue}}{{^-last}}, {{/-last}}{{/readWriteVars}})
|
||||
public {{classname}}({{#readWriteVars}}{{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}?{{/isContainer}}{{/isEnum}} {{name}} = {{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}default({{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}?{{/isContainer}}{{/isEnum}}){{/defaultValue}}{{^-last}}, {{/-last}}{{/readWriteVars}}){{#parent}} : base({{#parentVars}}{{name}}{{#hasMore}}, {{/hasMore}}{{/parentVars}}){{/parent}}
|
||||
{
|
||||
{{#vars}}
|
||||
{{^isInherited}}
|
||||
{{^isReadOnly}}
|
||||
{{#required}}
|
||||
// to ensure "{{name}}" is required (not null)
|
||||
@@ -65,8 +70,10 @@
|
||||
}
|
||||
{{/required}}
|
||||
{{/isReadOnly}}
|
||||
{{/isInherited}}
|
||||
{{/vars}}
|
||||
{{#vars}}
|
||||
{{^isInherited}}
|
||||
{{^isReadOnly}}
|
||||
{{^required}}
|
||||
{{#defaultValue}}// use default value if no "{{name}}" provided
|
||||
@@ -84,10 +91,12 @@ this.{{name}} = {{name}};
|
||||
{{/defaultValue}}
|
||||
{{/required}}
|
||||
{{/isReadOnly}}
|
||||
{{/isInherited}}
|
||||
{{/vars}}
|
||||
}
|
||||
|
||||
{{#vars}}
|
||||
{{^isInherited}}
|
||||
{{^isEnum}}
|
||||
/// <summary>
|
||||
/// {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{description}}{{/description}}
|
||||
@@ -97,6 +106,7 @@ this.{{name}} = {{name}};
|
||||
[JsonConverter(typeof(SwaggerDateConverter))]{{/isDate}}
|
||||
public {{{datatype}}} {{name}} { get; {{#isReadOnly}}private {{/isReadOnly}}set; }
|
||||
{{/isEnum}}
|
||||
{{/isInherited}}
|
||||
|
||||
{{/vars}}
|
||||
/// <summary>
|
||||
@@ -107,6 +117,9 @@ this.{{name}} = {{name}};
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("class {{classname}} {\n");
|
||||
{{#parent}}
|
||||
sb.Append(" ").Append(base.ToString().Replace("\n", "\n ")).Append("\n");
|
||||
{{/parent}}
|
||||
{{#vars}}
|
||||
sb.Append(" {{name}}: ").Append({{name}}).Append("\n");
|
||||
{{/vars}}
|
||||
@@ -118,7 +131,7 @@ this.{{name}} = {{name}};
|
||||
/// Returns the JSON string presentation of the object
|
||||
/// </summary>
|
||||
/// <returns>JSON string presentation of the object</returns>
|
||||
public {{#parent}} new {{/parent}}string ToJson()
|
||||
public {{#parent}}{{^isArrayModel}}override {{/isArrayModel}}{{/parent}}{{^parent}}{{#discriminator}}virtual {{/discriminator}}{{/parent}}string ToJson()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||
}
|
||||
@@ -143,7 +156,7 @@ this.{{name}} = {{name}};
|
||||
if (input == null)
|
||||
return false;
|
||||
|
||||
return {{#vars}}{{#isNotContainer}}
|
||||
return {{#vars}}{{#parent}}base.Equals(input) && {{/parent}}{{#isNotContainer}}
|
||||
(
|
||||
this.{{name}} == input.{{name}} ||
|
||||
(this.{{name}} != null &&
|
||||
@@ -151,9 +164,9 @@ this.{{name}} = {{name}};
|
||||
){{#hasMore}} && {{/hasMore}}{{/isNotContainer}}{{^isNotContainer}}
|
||||
(
|
||||
this.{{name}} == input.{{name}} ||
|
||||
(this.{{name}} != null &&
|
||||
this.{{name}}.SequenceEqual(input.{{name}}))
|
||||
){{#hasMore}} && {{/hasMore}}{{/isNotContainer}}{{/vars}}{{^vars}}false{{/vars}};
|
||||
this.{{name}} != null &&
|
||||
this.{{name}}.SequenceEqual(input.{{name}})
|
||||
){{#hasMore}} && {{/hasMore}}{{/isNotContainer}}{{/vars}}{{^vars}}{{#parent}}base.Equals(input){{/parent}}{{^parent}}false{{/parent}}{{/vars}};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -164,7 +177,12 @@ this.{{name}} = {{name}};
|
||||
{
|
||||
unchecked // Overflow is fine, just wrap
|
||||
{
|
||||
{{#parent}}
|
||||
int hashCode = base.GetHashCode();
|
||||
{{/parent}}
|
||||
{{^parent}}
|
||||
int hashCode = 41;
|
||||
{{/parent}}
|
||||
{{#vars}}
|
||||
if (this.{{name}} != null)
|
||||
hashCode = hashCode * 59 + this.{{name}}.GetHashCode();
|
||||
@@ -197,6 +215,7 @@ this.{{name}} = {{name}};
|
||||
|
||||
{{/generatePropertyChanged}}
|
||||
{{#validatable}}
|
||||
{{#discriminator}}
|
||||
/// <summary>
|
||||
/// To validate all properties of the instance
|
||||
/// </summary>
|
||||
@@ -204,6 +223,31 @@ this.{{name}} = {{name}};
|
||||
/// <returns>Validation Result</returns>
|
||||
IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
|
||||
{
|
||||
return this.BaseValidate(validationContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To validate all properties of the instance
|
||||
/// </summary>
|
||||
/// <param name="validationContext">Validation context</param>
|
||||
/// <returns>Validation Result</returns>
|
||||
protected IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> BaseValidate(ValidationContext validationContext)
|
||||
{
|
||||
{{/discriminator}}
|
||||
{{^discriminator}}
|
||||
/// <summary>
|
||||
/// To validate all properties of the instance
|
||||
/// </summary>
|
||||
/// <param name="validationContext">Validation context</param>
|
||||
/// <returns>Validation Result</returns>
|
||||
IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
|
||||
{
|
||||
{{/discriminator}}
|
||||
{{#parent}}
|
||||
{{^isArrayModel}}
|
||||
foreach(var x in BaseValidate(validationContext)) yield return x;
|
||||
{{/isArrayModel}}
|
||||
{{/parent}}
|
||||
{{#vars}}
|
||||
{{#hasValidation}}
|
||||
{{#maxLength}}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
{{#parent}}
|
||||
{{#parentVars}}
|
||||
**{{name}}** | {{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#readOnly}}[readonly] {{/readOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}}
|
||||
{{/parentVars}}
|
||||
{{/parent}}
|
||||
{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#readOnly}}[readonly] {{/readOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}}
|
||||
{{/vars}}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using {{packageName}}.{{apiPackage}};
|
||||
using {{packageName}}.{{modelPackage}};
|
||||
using {{packageName}}.Client;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
@@ -57,6 +58,20 @@ namespace {{packageName}}.Test
|
||||
//Assert.IsInstanceOfType<{{classname}}> (instance, "variable 'instance' is a {{classname}}");
|
||||
}
|
||||
|
||||
{{#discriminator}}
|
||||
{{#children}}
|
||||
/// <summary>
|
||||
/// Test deserialize a {{classname}} from type {{parent}}
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void {{classname}}DeserializeFrom{{parent}}Test()
|
||||
{
|
||||
// TODO uncomment below to test deserialize a {{classname}} from type {{parent}}
|
||||
//Assert.IsInstanceOf<{{parent}}>(JsonConvert.DeserializeObject<{{parent}}>(new {{classname}}().ToJson()));
|
||||
}
|
||||
{{/children}}
|
||||
{{/discriminator}}
|
||||
|
||||
{{#vars}}
|
||||
/// <summary>
|
||||
/// Test the property '{{name}}'
|
||||
|
||||
Reference in New Issue
Block a user