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

feat: add required field management in csharp newtonsoft serializer #2117

1 change: 1 addition & 0 deletions docs/languages/Csharp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Root>
Expand All @@ -21,8 +28,14 @@ public class RootConverter : JsonConverter<Root>
JObject jo = JObject.Load(reader);
Root value = new Root();

if(jo[\\"email\\"] != null) {
value.Email = jo[\\"email\\"].ToObject<string?>(serializer);
if(jo[\\"email\\"] is null){
throw new JsonSerializationException(\\"Required property 'email' is missing\\");
}

value.Email = jo[\\"email\\"].ToObject<string>(serializer);

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


Expand All @@ -36,6 +49,10 @@ public class RootConverter : JsonConverter<Root>
{
jo.Add(\\"email\\", JToken.FromObject(value.Email, serializer));
}
if (value.Name != null)
{
jo.Add(\\"name\\", JToken.FromObject(value.Name, serializer));
}


jo.WriteTo(writer);
Expand Down
13 changes: 12 additions & 1 deletion examples/csharp-generate-newtonsoft-serializer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
}
};
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/generators/csharp/CSharpGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface CSharpOptions extends CommonGeneratorOptions<CSharpPreset> {
autoImplementedProperties: boolean;
modelType: 'class' | 'record';
handleNullable: boolean;
enforceRequired: boolean;
}
export type CSharpConstantConstraint = ConstantConstraint<CSharpOptions>;
export type CSharpEnumKeyConstraint = EnumKeyConstraint<CSharpOptions>;
Expand Down Expand Up @@ -77,6 +78,7 @@ export class CSharpGenerator extends AbstractGenerator<
autoImplementedProperties: false,
handleNullable: false,
modelType: 'class',
enforceRequired: false,
// Temporarily set
dependencyManager: () => {
return {} as CSharpDependencyManager;
Expand Down
22 changes: 19 additions & 3 deletions src/generators/csharp/presets/NewtonsoftSerializerPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand All @@ -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};
}`;
Expand Down Expand Up @@ -151,15 +167,15 @@ function renderDeserialize({
export const CSHARP_NEWTONSOFT_SERIALIZER_PRESET: CSharpPreset<CSharpOptions> =
{
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(
'using System.Collections.Generic;'
);
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))]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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();
});
});
Loading
Loading