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/__snapshots__/index.spec.ts.snap b/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap index e13aa6942c..2778a9dd24 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,8 +28,14 @@ public class RootConverter : JsonConverter JObject jo = JObject.Load(reader); Root value = new Root(); - if(jo[\\"email\\"] != null) { - value.Email = jo[\\"email\\"].ToObject(serializer); + if(jo[\\"email\\"] is null){ + throw new JsonSerializationException(\\"Required property 'email' is missing\\"); +} + +value.Email = jo[\\"email\\"].ToObject(serializer); + +if(jo[\\"name\\"] != null) { + value.Name = jo[\\"name\\"].ToObject(serializer); } @@ -36,6 +49,10 @@ public class RootConverter : JsonConverter { jo.Add(\\"email\\", JToken.FromObject(value.Email, serializer)); } +if (value.Name != null) +{ + jo.Add(\\"name\\", JToken.FromObject(value.Name, serializer)); +} jo.WriteTo(writer); diff --git a/examples/csharp-generate-newtonsoft-serializer/index.ts b/examples/csharp-generate-newtonsoft-serializer/index.ts index f48930e31f..5b53a4c48a 100644 --- a/examples/csharp-generate-newtonsoft-serializer/index.ts +++ b/examples/csharp-generate-newtonsoft-serializer/index.ts @@ -4,17 +4,28 @@ 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, + required: ['email'], properties: { email: { type: 'string', format: 'email' + }, + name: { + type: 'string' } } }; 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": { diff --git a/src/generators/csharp/CSharpGenerator.ts b/src/generators/csharp/CSharpGenerator.ts index add9bab4ec..eb567ded3a 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; @@ -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..d052475240 100644 --- a/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts +++ b/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts @@ -71,9 +71,11 @@ 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 +98,20 @@ 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 +167,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 +175,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/NewtonsoftSerializerPreset.spec.ts b/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts index 08eae68dfb..2074d4c790 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/__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));