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

fix: csharp generator not render optional types for optional properties #1051

Merged
merged 10 commits into from
Dec 16, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ exports[`Should be able to render collections in C# as IEnumerable and should lo
Array [
"public class Root
{
private IEnumerable<dynamic> email;
private IEnumerable<dynamic>? email;

public IEnumerable<dynamic> Email
public IEnumerable<dynamic>? Email
{
get { return email; }
set { email = value; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ exports[`Should be able to generate a model to overwrite the Equal and GetHashCo
Array [
"public class Root
{
private string email;
private string? email;

public string Email
public string? Email
{
get { return email; }
set { email = value; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ Array [
"[JsonConverter(typeof(RootConverter))]
public class Root
{
private string email;
private string? email;

public string Email
public string? Email
{
get { return email; }
set { email = value; }
Expand Down Expand Up @@ -46,7 +46,7 @@ internal class RootConverter : JsonConverter<Root>
string propertyName = reader.GetString();
if (propertyName == \\"email\\")
{
var value = JsonSerializer.Deserialize<string>(ref reader, options);
var value = JsonSerializer.Deserialize<string?>(ref reader, options);
instance.email = value;
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ Array [
"[JsonConverter(typeof(RootConverter))]
public class Root
{
private string email;
private string? email;

public string Email
public string? Email
{
get { return email; }
set { email = value; }
Expand All @@ -22,7 +22,7 @@ public class RootConverter : JsonConverter<Root>
Root value = new Root();

if(jo[\\"email\\"] != null) {
value.Email = jo[\\"email\\"].ToObject<string>(serializer);
value.Email = jo[\\"email\\"].ToObject<string?>(serializer);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ Array [
"public class Root
{
private bool requiredBoolean;
private bool notRequiredBoolean;
private bool? notRequiredBoolean;
private string requiredString;
private string notRequiredString;
private string? notRequiredString;

public bool RequiredBoolean
{
get { return requiredBoolean; }
set { requiredBoolean = value; }
}

public bool NotRequiredBoolean
public bool? NotRequiredBoolean
{
get { return notRequiredBoolean; }
set { notRequiredBoolean = value; }
Expand All @@ -27,7 +27,7 @@ Array [
set { requiredString = value; }
}

public string NotRequiredString
public string? NotRequiredString
{
get { return notRequiredString; }
set { notRequiredString = value; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ exports[`Should be able to render inheritance and should log expected output to
Array [
"public class Root : IEvent
{
private string[] email;
private string[]? email;

public string[] Email
public string[]? Email
{
get { return email; }
set { email = value; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ exports[`Should be able to render C# models and should log expected output to c
Array [
"public class Root
{
private string email;
private string? email;

public string Email
public string? Email
{
get { return email; }
set { email = value; }
Expand Down
67 changes: 38 additions & 29 deletions src/generators/csharp/CSharpConstrainer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { ConstrainedEnumValueModel } from '../../models';
import { ConstrainedEnumValueModel, ConstrainedObjectPropertyModel } from '../../models';
import { TypeMapping } from '../../helpers';
import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer';
import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer';
import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer';
import { CSharpOptions } from './CSharpGenerator';

function getFullTypeDefinition(typeName: string, partOfProperty: ConstrainedObjectPropertyModel | undefined) {
return partOfProperty?.required ?? true
? typeName
: `${typeName}?`;
}

const fromEnumValueToType = (enumValueModel: ConstrainedEnumValueModel): string => {
switch (typeof enumValueModel.value) {
case 'boolean':
Expand All @@ -24,40 +30,42 @@ const fromEnumValueToType = (enumValueModel: ConstrainedEnumValueModel): string
};

export const CSharpDefaultTypeMapping: TypeMapping<CSharpOptions> = {
Object ({constrainedModel}): string {
return constrainedModel.name;

Object({ constrainedModel, partOfProperty }): string {
return getFullTypeDefinition(constrainedModel.name, partOfProperty);
},
Reference ({constrainedModel}): string {
return constrainedModel.name;
Reference({ constrainedModel, partOfProperty }): string {
return getFullTypeDefinition(constrainedModel.name, partOfProperty);
},
Any (): string {
return 'dynamic';
Any({ partOfProperty }): string {
return getFullTypeDefinition('dynamic', partOfProperty);
},
Float (): string {
return 'double';
Float({ partOfProperty }): string {
return getFullTypeDefinition('double', partOfProperty);
},
Integer (): string {
return 'int';
Integer({ partOfProperty }): string {
return getFullTypeDefinition('int', partOfProperty);
},
String (): string {
return 'string';
String({ partOfProperty }): string {
return getFullTypeDefinition('string', partOfProperty);
},
Boolean (): string {
return 'bool';
Boolean({ partOfProperty }): string {
return getFullTypeDefinition('bool', partOfProperty);
},
Tuple ({constrainedModel}): string {
Tuple({ constrainedModel, partOfProperty }): string {
const tupleTypes = constrainedModel.tuple.map((constrainedType) => {
return constrainedType.value.type;
});
return `(${tupleTypes.join(', ')})`;
return getFullTypeDefinition(`(${tupleTypes.join(', ')})`, partOfProperty);
},
Array ({constrainedModel, options}): string {
if (options.collectionType && options.collectionType === 'List') {
return `IEnumerable<${constrainedModel.valueModel.type}>`;
}
return `${constrainedModel.valueModel.type}[]`;
Array({ constrainedModel, options, partOfProperty }): string {
const typeString = options.collectionType && options.collectionType === 'List'
? `IEnumerable<${constrainedModel.valueModel.type}>`
: `${constrainedModel.valueModel.type}[]`;

return getFullTypeDefinition(typeString, partOfProperty);
},
Enum ({constrainedModel}): string {
Enum({ constrainedModel, partOfProperty }): string {
const typesForValues: Set<string> = new Set();

for (const value of constrainedModel.values) {
Expand All @@ -69,14 +77,15 @@ export const CSharpDefaultTypeMapping: TypeMapping<CSharpOptions> = {
return 'dynamic';
}
const [typeForValues] = typesForValues;
return typeForValues;
return getFullTypeDefinition(typeForValues, partOfProperty);
},
Union (): string {
//Because CSharp have no notion of unions (and no custom implementation), we have to render it as any value.
return 'dynamic';
Union({ partOfProperty }): string {
//BecauserenderPrivateType( CSharp , partOfProperty) have no notion of unions (and no custom implementation), we have to render it as any value.

return getFullTypeDefinition('dynamic', partOfProperty);
},
Dictionary ({constrainedModel}): string {
return `Dictionary<${constrainedModel.key.type}, ${constrainedModel.value.type}>`;
Dictionary({ constrainedModel, partOfProperty }): string {
return getFullTypeDefinition(`Dictionary<${constrainedModel.key.type}, ${constrainedModel.value.type}>`, partOfProperty);
}
};

Expand Down
6 changes: 1 addition & 5 deletions src/generators/csharp/CSharpRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AbstractRenderer } from '../AbstractRenderer';
import { CSharpGenerator, CSharpOptions } from './CSharpGenerator';
import { Preset, ConstrainedMetaModel, InputMetaModel, ConstrainedObjectPropertyModel } from '../../models';
import { Preset, ConstrainedMetaModel, InputMetaModel } from '../../models';
import { FormatHelpers } from '../../helpers/FormatHelpers';

/**
Expand All @@ -23,8 +23,4 @@ export abstract class CSharpRenderer<RendererModelType extends ConstrainedMetaMo
lines = FormatHelpers.breakLines(lines);
return lines.map(line => `// ${line}`).join('\n');
}

optionalMark(propertyModel: ConstrainedObjectPropertyModel): string {
return propertyModel.required ? '' : '?';
}
}
2 changes: 1 addition & 1 deletion src/generators/csharp/presets/JsonSerializerPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function renderDeserializeProperty(model: ConstrainedObjectPropertyModel) {
//Referenced enums is the only one who need custom serialization
if (model.property instanceof ConstrainedReferenceModel &&
model.property.ref instanceof ConstrainedEnumModel) {
return `${model.property.type}Extension.To${model.property.type}(JsonSerializer.Deserialize<dynamic>(ref reader, options))`;
return `${model.property.name}Extension.To${model.property.name}(JsonSerializer.Deserialize<dynamic>(ref reader, options))`;
}
return `JsonSerializer.Deserialize<${model.property.type}>(ref reader, options)`;
}
Expand Down
4 changes: 2 additions & 2 deletions src/generators/csharp/presets/NewtonsoftSerializerPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function renderSerialize({ model }: {
let toJson = `jo.Add("${prop.unconstrainedPropertyName}", JToken.FromObject(value.${propertyAccessor}, serializer));`;
if (prop.property instanceof ConstrainedReferenceModel
&& prop.property.ref instanceof ConstrainedEnumModel) {
toJson = `var enumValue = ${prop.property.type}Extensions.GetValue((${prop.property.type})value.${propertyAccessor});
toJson = `var enumValue = ${prop.property.name}Extensions.GetValue((${prop.property.name})value.${propertyAccessor});
var stringEnumValue = enumValue.ToString();
// C# converts booleans to uppercase True and False, which newtonsoft cannot understand
var jsonStringCompliant = stringEnumValue == "True" || stringEnumValue == "False" ? stringEnumValue.ToLower() : stringEnumValue;
Expand Down Expand Up @@ -68,7 +68,7 @@ function renderDeserialize({ model }: {
let toValue = `jo["${prop.unconstrainedPropertyName}"].ToObject<${prop.property.type}>(serializer)`;
if (prop.property instanceof ConstrainedReferenceModel
&& prop.property.ref instanceof ConstrainedEnumModel) {
toValue = `${prop.property.type}Extensions.To${prop.property.type}(jo["${prop.unconstrainedPropertyName}"])`;
toValue = `${prop.property.name}Extensions.To${prop.property.name}(jo["${prop.unconstrainedPropertyName}"])`;
}
return `if(jo["${prop.unconstrainedPropertyName}"] != null) {
value.${propertyAccessor} = ${toValue};
Expand Down
2 changes: 1 addition & 1 deletion src/generators/csharp/renderers/ClassRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const CSHARP_DEFAULT_CLASS_PRESET: CsharpClassPreset<CSharpOptions> = {
if (options?.autoImplementedProperties) {
const getter = await renderer.runGetterPreset(property);
const setter = await renderer.runSetterPreset(property);
return `public ${property.property.type}${property.required === false && '?'} ${pascalCase(property.propertyName)} { ${getter} ${setter} }`;
return `public ${property.property.type} ${pascalCase(property.propertyName)} { ${getter} ${setter} }`;
}
return `private ${property.property.type} ${property.propertyName};`;
},
Expand Down
Loading