From 47d118228cc0c3dc880e0194b62f456c70c49016 Mon Sep 17 00:00:00 2001 From: "francois.thiebaut" Date: Mon, 28 Oct 2024 17:20:13 +0100 Subject: [PATCH 1/7] feat: csharp newtonsoft generator - add option to enforce required field --- src/generators/csharp/CSharpGenerator.ts | 4 +- .../presets/NewtonsoftSerializerPreset.ts | 17 +- ...softSerializerPresetEnableRequired.spec.ts | 46 ++++ ...erializerPresetEnableRequired.spec.ts.snap | 220 ++++++++++++++++++ 4 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts create mode 100644 test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPresetEnableRequired.spec.ts.snap diff --git a/src/generators/csharp/CSharpGenerator.ts b/src/generators/csharp/CSharpGenerator.ts index add9bab4ec..7c224f666b 100644 --- a/src/generators/csharp/CSharpGenerator.ts +++ b/src/generators/csharp/CSharpGenerator.ts @@ -46,6 +46,7 @@ export interface CSharpOptions extends CommonGeneratorOptions { autoImplementedProperties: boolean; modelType: 'class' | 'record'; handleNullable: boolean; + enforceRequired: boolean; } export type CSharpConstantConstraint = ConstantConstraint; export type CSharpEnumKeyConstraint = EnumKeyConstraint; @@ -65,7 +66,7 @@ export interface CSharpRenderCompleteModelOptions { * Generator for CSharp */ export class CSharpGenerator extends AbstractGenerator< - CSharpOptions, + CSharpOptions, CSharpRenderCompleteModelOptions > { static defaultOptions: CSharpOptions = { @@ -77,6 +78,7 @@ export class CSharpGenerator extends AbstractGenerator< autoImplementedProperties: false, handleNullable: false, modelType: 'class', + enforceRequired:false, // Temporarily set dependencyManager: () => { return {} as CSharpDependencyManager; diff --git a/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts b/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts index 402d438484..172a620714 100644 --- a/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts +++ b/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts @@ -71,9 +71,10 @@ jo.Add("${prop.unconstrainedPropertyName}", JToken.FromObject(jsonStringComplian * Render `deserialize` function based on model */ function renderDeserialize({ - model + model, options }: { model: ConstrainedObjectModel; + options: CSharpOptions; }): string { const unwrapDictionaryProps = Object.values(model.properties).filter( (prop) => @@ -96,6 +97,16 @@ function renderDeserialize({ prop.unconstrainedPropertyName }"].ToString())${prop.required ? '.Value' : ''}`; } + + if(options?.enforceRequired !== undefined && options?.enforceRequired && prop.required) { + return `if(jo["${prop.unconstrainedPropertyName}"] is null){ + throw new JsonSerializationException("Required property '${prop.unconstrainedPropertyName}' is missing"); +} + +value.${propertyAccessor} = ${toValue}; +`; + } + return `if(jo["${prop.unconstrainedPropertyName}"] != null) { value.${propertyAccessor} = ${toValue}; }`; @@ -151,7 +162,7 @@ function renderDeserialize({ export const CSHARP_NEWTONSOFT_SERIALIZER_PRESET: CSharpPreset = { class: { - self: ({ renderer, content, model }) => { + self: ({ renderer, content, model, options }) => { renderer.dependencyManager.addDependency('using Newtonsoft.Json;'); renderer.dependencyManager.addDependency('using Newtonsoft.Json.Linq;'); renderer.dependencyManager.addDependency( @@ -159,7 +170,7 @@ export const CSHARP_NEWTONSOFT_SERIALIZER_PRESET: CSharpPreset = ); renderer.dependencyManager.addDependency('using System.Linq;'); - const deserialize = renderDeserialize({ model }); + const deserialize = renderDeserialize({ model, options }); const serialize = renderSerialize({ model }); return `[JsonConverter(typeof(${model.name}Converter))] diff --git a/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts b/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts new file mode 100644 index 0000000000..14938f297a --- /dev/null +++ b/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts @@ -0,0 +1,46 @@ +import { + CSharpGenerator, + CSHARP_NEWTONSOFT_SERIALIZER_PRESET +} from '../../../../src/generators'; +const doc = { + $id: 'Test', + type: 'object', + additionalProperties: true, + required: ['string prop'], + properties: { + 'string prop': { type: 'string' }, + numberProp: { type: 'number' }, + enumProp: { + $id: 'EnumTest', + enum: ['Some enum String', true, { test: 'test' }, 2] + }, + objectProp: { + type: 'object', + $id: 'NestedTest', + properties: { stringProp: { type: 'string' } } + } + }, + patternProperties: { + '^S(.?)test': { + type: 'string' + } + } +}; +describe('Newtonsoft JSON serializer preset with enforceRequired option enabled', () => { + test('should render serialize and deserialize converters', async () => { + const generator = new CSharpGenerator({ + presets: [{ + preset:CSHARP_NEWTONSOFT_SERIALIZER_PRESET, + options: { + enforceRequired:true + } + }] + }); + + const outputModels = await generator.generate(doc); + expect(outputModels).toHaveLength(3); + expect(outputModels[0].result).toMatchSnapshot(); + expect(outputModels[1].result).toMatchSnapshot(); + expect(outputModels[2].result).toMatchSnapshot(); + }); +}); diff --git a/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPresetEnableRequired.spec.ts.snap b/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPresetEnableRequired.spec.ts.snap new file mode 100644 index 0000000000..56a39888d1 --- /dev/null +++ b/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPresetEnableRequired.spec.ts.snap @@ -0,0 +1,220 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Newtonsoft JSON serializer preset with enforceRequired option enabled should render serialize and deserialize converters 1`] = ` +"[JsonConverter(typeof(TestConverter))] +public partial class Test +{ + private string stringProp; + private double? numberProp; + private EnumTest? enumProp; + private NestedTest? objectProp; + private Dictionary? additionalProperties; + + public string StringProp + { + get { return stringProp; } + set { this.stringProp = value; } + } + + public double? NumberProp + { + get { return numberProp; } + set { this.numberProp = value; } + } + + public EnumTest? EnumProp + { + get { return enumProp; } + set { this.enumProp = value; } + } + + public NestedTest? ObjectProp + { + get { return objectProp; } + set { this.objectProp = value; } + } + + public Dictionary? AdditionalProperties + { + get { return additionalProperties; } + set { this.additionalProperties = value; } + } +} + +public class TestConverter : JsonConverter +{ + public override Test ReadJson(JsonReader reader, System.Type objectType, Test existingValue, bool hasExistingValue, JsonSerializer serializer) +{ + JObject jo = JObject.Load(reader); + Test value = new Test(); + + if(jo[\\"string prop\\"] is null){ + throw new JsonSerializationException(\\"Required property 'string prop' is missing\\"); +} + +value.StringProp = jo[\\"string prop\\"].ToObject(serializer); + +if(jo[\\"numberProp\\"] != null) { + value.NumberProp = jo[\\"numberProp\\"].ToObject(serializer); +} +if(jo[\\"enumProp\\"] != null) { + value.EnumProp = EnumTestExtensions.ToEnumTest(jo[\\"enumProp\\"].ToString()); +} +if(jo[\\"objectProp\\"] != null) { + value.ObjectProp = jo[\\"objectProp\\"].ToObject(serializer); +} + + var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"string prop\\" || prop.Name != \\"numberProp\\" || prop.Name != \\"enumProp\\" || prop.Name != \\"objectProp\\"); + value.AdditionalProperties = new Dictionary(); + + foreach (var additionalProperty in additionalProperties) + { + value.AdditionalProperties[additionalProperty.Name] = additionalProperty.Value.ToObject(serializer); + } + return value; +} + public override void WriteJson(JsonWriter writer, Test value, JsonSerializer serializer) +{ + JObject jo = new JObject(); + + if (value.StringProp != null) +{ + jo.Add(\\"string prop\\", JToken.FromObject(value.StringProp, serializer)); +} +if (value.NumberProp != null) +{ + jo.Add(\\"numberProp\\", JToken.FromObject(value.NumberProp, serializer)); +} +if (value.EnumProp != null) +{ + var enumValue = EnumTestExtensions.GetValue((EnumTest)value.EnumProp); +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; +jo.Add(\\"enumProp\\", JToken.FromObject(jsonStringCompliant, serializer)); +} +if (value.ObjectProp != null) +{ + jo.Add(\\"objectProp\\", JToken.FromObject(value.ObjectProp, serializer)); +} + if (value.AdditionalProperties != null) + { + foreach (var unwrapProperty in value.AdditionalProperties) + { + var hasProp = jo[unwrapProperty.Key]; + if (hasProp != null) continue; + jo.Add(unwrapProperty.Key, JToken.FromObject(unwrapProperty.Value, serializer)); + } +} + + jo.WriteTo(writer); +} + + public override bool CanRead => true; + public override bool CanWrite => true; +}" +`; + +exports[`Newtonsoft JSON serializer preset with enforceRequired option enabled should render serialize and deserialize converters 2`] = ` +"public enum EnumTest +{ + SOME_SPACE_ENUM_SPACE_STRING, + RESERVED_TRUE, + CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT, + NUMBER_2 +} + +public static class EnumTestExtensions +{ + public static dynamic? GetValue(this EnumTest enumValue) + { + switch (enumValue) + { + case EnumTest.SOME_SPACE_ENUM_SPACE_STRING: return \\"Some enum String\\"; + case EnumTest.RESERVED_TRUE: return true; + case EnumTest.CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT: return \\"{\\\\\\"test\\\\\\":\\\\\\"test\\\\\\"}\\"; + case EnumTest.NUMBER_2: return 2; + } + return null; + } + + public static EnumTest? ToEnumTest(dynamic? value) + { + switch (value) + { + case \\"Some enum String\\": return EnumTest.SOME_SPACE_ENUM_SPACE_STRING; + case true: return EnumTest.RESERVED_TRUE; + case \\"{\\\\\\"test\\\\\\":\\\\\\"test\\\\\\"}\\": return EnumTest.CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT; + case 2: return EnumTest.NUMBER_2; + } + return null; + } +} +" +`; + +exports[`Newtonsoft JSON serializer preset with enforceRequired option enabled should render serialize and deserialize converters 3`] = ` +"[JsonConverter(typeof(NestedTestConverter))] +public partial class NestedTest +{ + private string? stringProp; + private Dictionary? additionalProperties; + + public string? StringProp + { + get { return stringProp; } + set { this.stringProp = value; } + } + + public Dictionary? AdditionalProperties + { + get { return additionalProperties; } + set { this.additionalProperties = value; } + } +} + +public class NestedTestConverter : JsonConverter +{ + public override NestedTest ReadJson(JsonReader reader, System.Type objectType, NestedTest existingValue, bool hasExistingValue, JsonSerializer serializer) +{ + JObject jo = JObject.Load(reader); + NestedTest value = new NestedTest(); + + if(jo[\\"stringProp\\"] != null) { + value.StringProp = jo[\\"stringProp\\"].ToObject(serializer); +} + + var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"stringProp\\"); + value.AdditionalProperties = new Dictionary(); + + foreach (var additionalProperty in additionalProperties) + { + value.AdditionalProperties[additionalProperty.Name] = additionalProperty.Value.ToObject(serializer); + } + return value; +} + public override void WriteJson(JsonWriter writer, NestedTest value, JsonSerializer serializer) +{ + JObject jo = new JObject(); + + if (value.StringProp != null) +{ + jo.Add(\\"stringProp\\", JToken.FromObject(value.StringProp, serializer)); +} + if (value.AdditionalProperties != null) + { + foreach (var unwrapProperty in value.AdditionalProperties) + { + var hasProp = jo[unwrapProperty.Key]; + if (hasProp != null) continue; + jo.Add(unwrapProperty.Key, JToken.FromObject(unwrapProperty.Value, serializer)); + } +} + + jo.WriteTo(writer); +} + + public override bool CanRead => true; + public override bool CanWrite => true; +}" +`; From b61906f140e77d6b1fb789ade3a093fd0d58a39b Mon Sep 17 00:00:00 2001 From: "francois.thiebaut" Date: Tue, 29 Oct 2024 10:00:32 +0100 Subject: [PATCH 2/7] feat: csharp newtonsoft generator - add enforceRequired in doc and example --- docs/languages/Csharp.md | 1 + .../csharp-generate-newtonsoft-serializer/index.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/languages/Csharp.md b/docs/languages/Csharp.md index bf48c37e9d..5434784e36 100644 --- a/docs/languages/Csharp.md +++ b/docs/languages/Csharp.md @@ -47,6 +47,7 @@ Requires [System.Text.Json](https://devblogs.microsoft.com/dotnet/try-the-new-sy #### Using Newtonsoft/Json.NET To include functionality that convert the models using the [Newtonsoft/Json.NET](https://www.newtonsoft.com/json) framework, to use this, use the preset `CSHARP_NEWTONSOFT_SERIALIZER_PRESET`. +You can use the option "enforceRequired" to prevent deserialization if any required field is missing. Check out this [example for a live demonstration](../../examples/csharp-generate-newtonsoft-serializer). diff --git a/examples/csharp-generate-newtonsoft-serializer/index.ts b/examples/csharp-generate-newtonsoft-serializer/index.ts index f48930e31f..4575614d93 100644 --- a/examples/csharp-generate-newtonsoft-serializer/index.ts +++ b/examples/csharp-generate-newtonsoft-serializer/index.ts @@ -4,17 +4,26 @@ import { } from '../../src'; const generator = new CSharpGenerator({ - presets: [CSHARP_NEWTONSOFT_SERIALIZER_PRESET] + presets: [{ + preset:CSHARP_NEWTONSOFT_SERIALIZER_PRESET, + options: { + enforceRequired:true + } + }] }); const jsonSchemaDraft7 = { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', additionalProperties: false, - properties: { + required:["email"], + properties: { email: { type: 'string', format: 'email' + }, + name: { + type: 'string' } } }; From 3868045384f215440705ab93fec3a392fe644338 Mon Sep 17 00:00:00 2001 From: "francois.thiebaut" Date: Tue, 29 Oct 2024 10:38:13 +0100 Subject: [PATCH 3/7] feat: csharp newtonsoft generator - adapt example snapshot --- .../__snapshots__/index.spec.ts.snap | 17 +++++++++++++++-- .../package-lock.json | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap b/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap index e13aa6942c..019e0630b8 100644 --- a/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap +++ b/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap @@ -5,13 +5,20 @@ Array [ "[JsonConverter(typeof(RootConverter))] public partial class Root { - private string? email; + private string email; + private string? name; - public string? Email + public string Email { get { return email; } set { this.email = value; } } + + public string? Name + { + get { return name; } + set { this.name = value; } + } } public class RootConverter : JsonConverter @@ -21,6 +28,12 @@ public class RootConverter : JsonConverter JObject jo = JObject.Load(reader); Root value = new Root(); + if(jo[\\"email\\"] is null) { + throw new JsonSerializationException("Required property 'email' is missing"); +} + +value.Email = jo[\\"email\\"].ToObject(serializer); + if(jo[\\"email\\"] != null) { value.Email = jo[\\"email\\"].ToObject(serializer); } diff --git a/examples/csharp-generate-newtonsoft-serializer/package-lock.json b/examples/csharp-generate-newtonsoft-serializer/package-lock.json index 98c488824f..d3da444b5d 100644 --- a/examples/csharp-generate-newtonsoft-serializer/package-lock.json +++ b/examples/csharp-generate-newtonsoft-serializer/package-lock.json @@ -1,5 +1,5 @@ { - "name": "csharp-generate-serializer", + "name": "csharp-generate-newtonsoft-serializer", "lockfileVersion": 2, "requires": true, "packages": { From 6ffba4ad25cc60a5d78889b45ad6a8e616c3175c Mon Sep 17 00:00:00 2001 From: "francois.thiebaut" Date: Tue, 29 Oct 2024 10:57:19 +0100 Subject: [PATCH 4/7] feat: csharp newtonsoft generator - fix formatting --- .../index.ts | 18 ++++++++++-------- src/generators/csharp/CSharpGenerator.ts | 4 ++-- .../presets/NewtonsoftSerializerPreset.ts | 13 +++++++++---- ...nsoftSerializerPresetEnableRequired.spec.ts | 14 ++++++++------ 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/examples/csharp-generate-newtonsoft-serializer/index.ts b/examples/csharp-generate-newtonsoft-serializer/index.ts index 4575614d93..5b53a4c48a 100644 --- a/examples/csharp-generate-newtonsoft-serializer/index.ts +++ b/examples/csharp-generate-newtonsoft-serializer/index.ts @@ -4,26 +4,28 @@ import { } from '../../src'; const generator = new CSharpGenerator({ - presets: [{ - preset:CSHARP_NEWTONSOFT_SERIALIZER_PRESET, - options: { - enforceRequired:true + presets: [ + { + preset: CSHARP_NEWTONSOFT_SERIALIZER_PRESET, + options: { + enforceRequired: true + } } - }] + ] }); const jsonSchemaDraft7 = { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', additionalProperties: false, - required:["email"], - properties: { + required: ['email'], + properties: { email: { type: 'string', format: 'email' }, name: { - type: 'string' + type: 'string' } } }; diff --git a/src/generators/csharp/CSharpGenerator.ts b/src/generators/csharp/CSharpGenerator.ts index 7c224f666b..eb567ded3a 100644 --- a/src/generators/csharp/CSharpGenerator.ts +++ b/src/generators/csharp/CSharpGenerator.ts @@ -66,7 +66,7 @@ export interface CSharpRenderCompleteModelOptions { * Generator for CSharp */ export class CSharpGenerator extends AbstractGenerator< - CSharpOptions, + CSharpOptions, CSharpRenderCompleteModelOptions > { static defaultOptions: CSharpOptions = { @@ -78,7 +78,7 @@ export class CSharpGenerator extends AbstractGenerator< autoImplementedProperties: false, handleNullable: false, modelType: 'class', - enforceRequired:false, + enforceRequired: false, // Temporarily set dependencyManager: () => { return {} as CSharpDependencyManager; diff --git a/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts b/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts index 172a620714..d052475240 100644 --- a/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts +++ b/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts @@ -71,7 +71,8 @@ jo.Add("${prop.unconstrainedPropertyName}", JToken.FromObject(jsonStringComplian * Render `deserialize` function based on model */ function renderDeserialize({ - model, options + model, + options }: { model: ConstrainedObjectModel; options: CSharpOptions; @@ -97,8 +98,12 @@ function renderDeserialize({ prop.unconstrainedPropertyName }"].ToString())${prop.required ? '.Value' : ''}`; } - - if(options?.enforceRequired !== undefined && options?.enforceRequired && prop.required) { + + if ( + options?.enforceRequired !== undefined && + options?.enforceRequired && + prop.required + ) { return `if(jo["${prop.unconstrainedPropertyName}"] is null){ throw new JsonSerializationException("Required property '${prop.unconstrainedPropertyName}' is missing"); } @@ -106,7 +111,7 @@ function renderDeserialize({ value.${propertyAccessor} = ${toValue}; `; } - + return `if(jo["${prop.unconstrainedPropertyName}"] != null) { value.${propertyAccessor} = ${toValue}; }`; diff --git a/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts b/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts index 14938f297a..68ba6500bc 100644 --- a/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts +++ b/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts @@ -29,16 +29,18 @@ const doc = { describe('Newtonsoft JSON serializer preset with enforceRequired option enabled', () => { test('should render serialize and deserialize converters', async () => { const generator = new CSharpGenerator({ - presets: [{ - preset:CSHARP_NEWTONSOFT_SERIALIZER_PRESET, - options: { - enforceRequired:true + presets: [ + { + preset: CSHARP_NEWTONSOFT_SERIALIZER_PRESET, + options: { + enforceRequired: true + } } - }] + ] }); const outputModels = await generator.generate(doc); - expect(outputModels).toHaveLength(3); + expect(outputModels).toHaveLength(3); expect(outputModels[0].result).toMatchSnapshot(); expect(outputModels[1].result).toMatchSnapshot(); expect(outputModels[2].result).toMatchSnapshot(); From 9172611e01140d59e1566365d621173769b58826 Mon Sep 17 00:00:00 2001 From: "francois.thiebaut" Date: Tue, 29 Oct 2024 11:22:04 +0100 Subject: [PATCH 5/7] feat:csharp newtonsoft generator - adapt formatting --- .../__snapshots__/index.spec.ts.snap | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap b/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap index 019e0630b8..2778a9dd24 100644 --- a/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap +++ b/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap @@ -14,7 +14,7 @@ public partial class Root set { this.email = value; } } - public string? Name + public string? Name { get { return name; } set { this.name = value; } @@ -28,14 +28,14 @@ public class RootConverter : JsonConverter JObject jo = JObject.Load(reader); Root value = new Root(); - if(jo[\\"email\\"] is null) { - throw new JsonSerializationException("Required property 'email' is missing"); + if(jo[\\"email\\"] is null){ + throw new JsonSerializationException(\\"Required property 'email' is missing\\"); } -value.Email = jo[\\"email\\"].ToObject(serializer); +value.Email = jo[\\"email\\"].ToObject(serializer); - if(jo[\\"email\\"] != null) { - value.Email = jo[\\"email\\"].ToObject(serializer); +if(jo[\\"name\\"] != null) { + value.Name = jo[\\"name\\"].ToObject(serializer); } @@ -49,6 +49,10 @@ value.Email = jo[\\"email\\"].ToObject(serializer); { jo.Add(\\"email\\", JToken.FromObject(value.Email, serializer)); } +if (value.Name != null) +{ + jo.Add(\\"name\\", JToken.FromObject(value.Name, serializer)); +} jo.WriteTo(writer); From 0f8a0bf3080eb4282a4a4abcde3e3eefd013f1eb Mon Sep 17 00:00:00 2001 From: "francois.thiebaut" Date: Tue, 29 Oct 2024 11:39:26 +0100 Subject: [PATCH 6/7] feat: csharp newtonsoft generator - remove duplicated test and improve existing one --- .../NewtonsoftSerializerPreset.spec.ts | 10 +- ...softSerializerPresetEnableRequired.spec.ts | 48 ---- .../NewtonsoftSerializerPreset.spec.ts.snap | 23 +- ...erializerPresetEnableRequired.spec.ts.snap | 220 ------------------ 4 files changed, 29 insertions(+), 272 deletions(-) delete mode 100644 test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts delete mode 100644 test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPresetEnableRequired.spec.ts.snap diff --git a/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts b/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts index 08eae68dfb..ee64fffb6e 100644 --- a/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts +++ b/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts @@ -9,6 +9,7 @@ const doc = { required: ['string prop'], properties: { 'string prop': { type: 'string' }, + notRequiredStringProp : { type: 'string' }, numberProp: { type: 'number' }, enumProp: { $id: 'EnumTest', @@ -29,7 +30,14 @@ const doc = { describe('Newtonsoft JSON serializer preset', () => { test('should render serialize and deserialize converters', async () => { const generator = new CSharpGenerator({ - presets: [CSHARP_NEWTONSOFT_SERIALIZER_PRESET] + presets: [ + { + preset: CSHARP_NEWTONSOFT_SERIALIZER_PRESET, + options: { + enforceRequired: true + } + } + ] }); const outputModels = await generator.generate(doc); diff --git a/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts b/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts deleted file mode 100644 index 68ba6500bc..0000000000 --- a/test/generators/csharp/presets/NewtonsoftSerializerPresetEnableRequired.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { - CSharpGenerator, - CSHARP_NEWTONSOFT_SERIALIZER_PRESET -} from '../../../../src/generators'; -const doc = { - $id: 'Test', - type: 'object', - additionalProperties: true, - required: ['string prop'], - properties: { - 'string prop': { type: 'string' }, - numberProp: { type: 'number' }, - enumProp: { - $id: 'EnumTest', - enum: ['Some enum String', true, { test: 'test' }, 2] - }, - objectProp: { - type: 'object', - $id: 'NestedTest', - properties: { stringProp: { type: 'string' } } - } - }, - patternProperties: { - '^S(.?)test': { - type: 'string' - } - } -}; -describe('Newtonsoft JSON serializer preset with enforceRequired option enabled', () => { - test('should render serialize and deserialize converters', async () => { - const generator = new CSharpGenerator({ - presets: [ - { - preset: CSHARP_NEWTONSOFT_SERIALIZER_PRESET, - options: { - enforceRequired: true - } - } - ] - }); - - const outputModels = await generator.generate(doc); - expect(outputModels).toHaveLength(3); - expect(outputModels[0].result).toMatchSnapshot(); - expect(outputModels[1].result).toMatchSnapshot(); - expect(outputModels[2].result).toMatchSnapshot(); - }); -}); diff --git a/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap b/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap index c6b1af16bf..7e88ffe441 100644 --- a/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap +++ b/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap @@ -5,6 +5,7 @@ exports[`Newtonsoft JSON serializer preset should render serialize and deseriali public partial class Test { private string stringProp; + private string? notRequiredStringProp; private double? numberProp; private EnumTest? enumProp; private NestedTest? objectProp; @@ -16,6 +17,12 @@ public partial class Test set { this.stringProp = value; } } + public string? NotRequiredStringProp + { + get { return notRequiredStringProp; } + set { this.notRequiredStringProp = value; } + } + public double? NumberProp { get { return numberProp; } @@ -48,8 +55,14 @@ public class TestConverter : JsonConverter JObject jo = JObject.Load(reader); Test value = new Test(); - if(jo[\\"string prop\\"] != null) { - value.StringProp = jo[\\"string prop\\"].ToObject(serializer); + if(jo[\\"string prop\\"] is null){ + throw new JsonSerializationException(\\"Required property 'string prop' is missing\\"); +} + +value.StringProp = jo[\\"string prop\\"].ToObject(serializer); + +if(jo[\\"notRequiredStringProp\\"] != null) { + value.NotRequiredStringProp = jo[\\"notRequiredStringProp\\"].ToObject(serializer); } if(jo[\\"numberProp\\"] != null) { value.NumberProp = jo[\\"numberProp\\"].ToObject(serializer); @@ -61,7 +74,7 @@ if(jo[\\"objectProp\\"] != null) { value.ObjectProp = jo[\\"objectProp\\"].ToObject(serializer); } - var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"string prop\\" || prop.Name != \\"numberProp\\" || prop.Name != \\"enumProp\\" || prop.Name != \\"objectProp\\"); + var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"string prop\\" || prop.Name != \\"notRequiredStringProp\\" || prop.Name != \\"numberProp\\" || prop.Name != \\"enumProp\\" || prop.Name != \\"objectProp\\"); value.AdditionalProperties = new Dictionary(); foreach (var additionalProperty in additionalProperties) @@ -78,6 +91,10 @@ if(jo[\\"objectProp\\"] != null) { { jo.Add(\\"string prop\\", JToken.FromObject(value.StringProp, serializer)); } +if (value.NotRequiredStringProp != null) +{ + jo.Add(\\"notRequiredStringProp\\", JToken.FromObject(value.NotRequiredStringProp, serializer)); +} if (value.NumberProp != null) { jo.Add(\\"numberProp\\", JToken.FromObject(value.NumberProp, serializer)); diff --git a/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPresetEnableRequired.spec.ts.snap b/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPresetEnableRequired.spec.ts.snap deleted file mode 100644 index 56a39888d1..0000000000 --- a/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPresetEnableRequired.spec.ts.snap +++ /dev/null @@ -1,220 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Newtonsoft JSON serializer preset with enforceRequired option enabled should render serialize and deserialize converters 1`] = ` -"[JsonConverter(typeof(TestConverter))] -public partial class Test -{ - private string stringProp; - private double? numberProp; - private EnumTest? enumProp; - private NestedTest? objectProp; - private Dictionary? additionalProperties; - - public string StringProp - { - get { return stringProp; } - set { this.stringProp = value; } - } - - public double? NumberProp - { - get { return numberProp; } - set { this.numberProp = value; } - } - - public EnumTest? EnumProp - { - get { return enumProp; } - set { this.enumProp = value; } - } - - public NestedTest? ObjectProp - { - get { return objectProp; } - set { this.objectProp = value; } - } - - public Dictionary? AdditionalProperties - { - get { return additionalProperties; } - set { this.additionalProperties = value; } - } -} - -public class TestConverter : JsonConverter -{ - public override Test ReadJson(JsonReader reader, System.Type objectType, Test existingValue, bool hasExistingValue, JsonSerializer serializer) -{ - JObject jo = JObject.Load(reader); - Test value = new Test(); - - if(jo[\\"string prop\\"] is null){ - throw new JsonSerializationException(\\"Required property 'string prop' is missing\\"); -} - -value.StringProp = jo[\\"string prop\\"].ToObject(serializer); - -if(jo[\\"numberProp\\"] != null) { - value.NumberProp = jo[\\"numberProp\\"].ToObject(serializer); -} -if(jo[\\"enumProp\\"] != null) { - value.EnumProp = EnumTestExtensions.ToEnumTest(jo[\\"enumProp\\"].ToString()); -} -if(jo[\\"objectProp\\"] != null) { - value.ObjectProp = jo[\\"objectProp\\"].ToObject(serializer); -} - - var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"string prop\\" || prop.Name != \\"numberProp\\" || prop.Name != \\"enumProp\\" || prop.Name != \\"objectProp\\"); - value.AdditionalProperties = new Dictionary(); - - foreach (var additionalProperty in additionalProperties) - { - value.AdditionalProperties[additionalProperty.Name] = additionalProperty.Value.ToObject(serializer); - } - return value; -} - public override void WriteJson(JsonWriter writer, Test value, JsonSerializer serializer) -{ - JObject jo = new JObject(); - - if (value.StringProp != null) -{ - jo.Add(\\"string prop\\", JToken.FromObject(value.StringProp, serializer)); -} -if (value.NumberProp != null) -{ - jo.Add(\\"numberProp\\", JToken.FromObject(value.NumberProp, serializer)); -} -if (value.EnumProp != null) -{ - var enumValue = EnumTestExtensions.GetValue((EnumTest)value.EnumProp); -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; -jo.Add(\\"enumProp\\", JToken.FromObject(jsonStringCompliant, serializer)); -} -if (value.ObjectProp != null) -{ - jo.Add(\\"objectProp\\", JToken.FromObject(value.ObjectProp, serializer)); -} - if (value.AdditionalProperties != null) - { - foreach (var unwrapProperty in value.AdditionalProperties) - { - var hasProp = jo[unwrapProperty.Key]; - if (hasProp != null) continue; - jo.Add(unwrapProperty.Key, JToken.FromObject(unwrapProperty.Value, serializer)); - } -} - - jo.WriteTo(writer); -} - - public override bool CanRead => true; - public override bool CanWrite => true; -}" -`; - -exports[`Newtonsoft JSON serializer preset with enforceRequired option enabled should render serialize and deserialize converters 2`] = ` -"public enum EnumTest -{ - SOME_SPACE_ENUM_SPACE_STRING, - RESERVED_TRUE, - CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT, - NUMBER_2 -} - -public static class EnumTestExtensions -{ - public static dynamic? GetValue(this EnumTest enumValue) - { - switch (enumValue) - { - case EnumTest.SOME_SPACE_ENUM_SPACE_STRING: return \\"Some enum String\\"; - case EnumTest.RESERVED_TRUE: return true; - case EnumTest.CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT: return \\"{\\\\\\"test\\\\\\":\\\\\\"test\\\\\\"}\\"; - case EnumTest.NUMBER_2: return 2; - } - return null; - } - - public static EnumTest? ToEnumTest(dynamic? value) - { - switch (value) - { - case \\"Some enum String\\": return EnumTest.SOME_SPACE_ENUM_SPACE_STRING; - case true: return EnumTest.RESERVED_TRUE; - case \\"{\\\\\\"test\\\\\\":\\\\\\"test\\\\\\"}\\": return EnumTest.CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT; - case 2: return EnumTest.NUMBER_2; - } - return null; - } -} -" -`; - -exports[`Newtonsoft JSON serializer preset with enforceRequired option enabled should render serialize and deserialize converters 3`] = ` -"[JsonConverter(typeof(NestedTestConverter))] -public partial class NestedTest -{ - private string? stringProp; - private Dictionary? additionalProperties; - - public string? StringProp - { - get { return stringProp; } - set { this.stringProp = value; } - } - - public Dictionary? AdditionalProperties - { - get { return additionalProperties; } - set { this.additionalProperties = value; } - } -} - -public class NestedTestConverter : JsonConverter -{ - public override NestedTest ReadJson(JsonReader reader, System.Type objectType, NestedTest existingValue, bool hasExistingValue, JsonSerializer serializer) -{ - JObject jo = JObject.Load(reader); - NestedTest value = new NestedTest(); - - if(jo[\\"stringProp\\"] != null) { - value.StringProp = jo[\\"stringProp\\"].ToObject(serializer); -} - - var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"stringProp\\"); - value.AdditionalProperties = new Dictionary(); - - foreach (var additionalProperty in additionalProperties) - { - value.AdditionalProperties[additionalProperty.Name] = additionalProperty.Value.ToObject(serializer); - } - return value; -} - public override void WriteJson(JsonWriter writer, NestedTest value, JsonSerializer serializer) -{ - JObject jo = new JObject(); - - if (value.StringProp != null) -{ - jo.Add(\\"stringProp\\", JToken.FromObject(value.StringProp, serializer)); -} - if (value.AdditionalProperties != null) - { - foreach (var unwrapProperty in value.AdditionalProperties) - { - var hasProp = jo[unwrapProperty.Key]; - if (hasProp != null) continue; - jo.Add(unwrapProperty.Key, JToken.FromObject(unwrapProperty.Value, serializer)); - } -} - - jo.WriteTo(writer); -} - - public override bool CanRead => true; - public override bool CanWrite => true; -}" -`; From 7bf5f908e038b1d990a80afb8b7f6d1ad61e814a Mon Sep 17 00:00:00 2001 From: "francois.thiebaut" Date: Tue, 29 Oct 2024 11:40:58 +0100 Subject: [PATCH 7/7] feat: csharp newtonsoft generator - fix formatting --- .../csharp/presets/NewtonsoftSerializerPreset.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts b/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts index ee64fffb6e..2074d4c790 100644 --- a/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts +++ b/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts @@ -9,7 +9,7 @@ const doc = { required: ['string prop'], properties: { 'string prop': { type: 'string' }, - notRequiredStringProp : { type: 'string' }, + notRequiredStringProp: { type: 'string' }, numberProp: { type: 'number' }, enumProp: { $id: 'EnumTest',