Skip to content

Commit 4db3328

Browse files
authored
feat: add required field management in csharp newtonsoft serializer (#2117)
1 parent c91d64d commit 4db3328

File tree

8 files changed

+85
-13
lines changed

8 files changed

+85
-13
lines changed

docs/languages/Csharp.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Requires [System.Text.Json](https://devblogs.microsoft.com/dotnet/try-the-new-sy
4747
#### Using Newtonsoft/Json.NET
4848

4949
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`.
50+
You can use the option "enforceRequired" to prevent deserialization if any required field is missing.
5051

5152
Check out this [example for a live demonstration](../../examples/csharp-generate-newtonsoft-serializer).
5253

examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ Array [
55
"[JsonConverter(typeof(RootConverter))]
66
public partial class Root
77
{
8-
private string? email;
8+
private string email;
9+
private string? name;
910
10-
public string? Email
11+
public string Email
1112
{
1213
get { return email; }
1314
set { this.email = value; }
1415
}
16+
17+
public string? Name
18+
{
19+
get { return name; }
20+
set { this.name = value; }
21+
}
1522
}
1623
1724
public class RootConverter : JsonConverter<Root>
@@ -21,8 +28,14 @@ public class RootConverter : JsonConverter<Root>
2128
JObject jo = JObject.Load(reader);
2229
Root value = new Root();
2330
24-
if(jo[\\"email\\"] != null) {
25-
value.Email = jo[\\"email\\"].ToObject<string?>(serializer);
31+
if(jo[\\"email\\"] is null){
32+
throw new JsonSerializationException(\\"Required property 'email' is missing\\");
33+
}
34+
35+
value.Email = jo[\\"email\\"].ToObject<string>(serializer);
36+
37+
if(jo[\\"name\\"] != null) {
38+
value.Name = jo[\\"name\\"].ToObject<string?>(serializer);
2639
}
2740
2841
@@ -36,6 +49,10 @@ public class RootConverter : JsonConverter<Root>
3649
{
3750
jo.Add(\\"email\\", JToken.FromObject(value.Email, serializer));
3851
}
52+
if (value.Name != null)
53+
{
54+
jo.Add(\\"name\\", JToken.FromObject(value.Name, serializer));
55+
}
3956
4057
4158
jo.WriteTo(writer);

examples/csharp-generate-newtonsoft-serializer/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,28 @@ import {
44
} from '../../src';
55

66
const generator = new CSharpGenerator({
7-
presets: [CSHARP_NEWTONSOFT_SERIALIZER_PRESET]
7+
presets: [
8+
{
9+
preset: CSHARP_NEWTONSOFT_SERIALIZER_PRESET,
10+
options: {
11+
enforceRequired: true
12+
}
13+
}
14+
]
815
});
916

1017
const jsonSchemaDraft7 = {
1118
$schema: 'http://json-schema.org/draft-07/schema#',
1219
type: 'object',
1320
additionalProperties: false,
21+
required: ['email'],
1422
properties: {
1523
email: {
1624
type: 'string',
1725
format: 'email'
26+
},
27+
name: {
28+
type: 'string'
1829
}
1930
}
2031
};

examples/csharp-generate-newtonsoft-serializer/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/generators/csharp/CSharpGenerator.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface CSharpOptions extends CommonGeneratorOptions<CSharpPreset> {
4646
autoImplementedProperties: boolean;
4747
modelType: 'class' | 'record';
4848
handleNullable: boolean;
49+
enforceRequired: boolean;
4950
}
5051
export type CSharpConstantConstraint = ConstantConstraint<CSharpOptions>;
5152
export type CSharpEnumKeyConstraint = EnumKeyConstraint<CSharpOptions>;
@@ -77,6 +78,7 @@ export class CSharpGenerator extends AbstractGenerator<
7778
autoImplementedProperties: false,
7879
handleNullable: false,
7980
modelType: 'class',
81+
enforceRequired: false,
8082
// Temporarily set
8183
dependencyManager: () => {
8284
return {} as CSharpDependencyManager;

src/generators/csharp/presets/NewtonsoftSerializerPreset.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ jo.Add("${prop.unconstrainedPropertyName}", JToken.FromObject(jsonStringComplian
7171
* Render `deserialize` function based on model
7272
*/
7373
function renderDeserialize({
74-
model
74+
model,
75+
options
7576
}: {
7677
model: ConstrainedObjectModel;
78+
options: CSharpOptions;
7779
}): string {
7880
const unwrapDictionaryProps = Object.values(model.properties).filter(
7981
(prop) =>
@@ -96,6 +98,20 @@ function renderDeserialize({
9698
prop.unconstrainedPropertyName
9799
}"].ToString())${prop.required ? '.Value' : ''}`;
98100
}
101+
102+
if (
103+
options?.enforceRequired !== undefined &&
104+
options?.enforceRequired &&
105+
prop.required
106+
) {
107+
return `if(jo["${prop.unconstrainedPropertyName}"] is null){
108+
throw new JsonSerializationException("Required property '${prop.unconstrainedPropertyName}' is missing");
109+
}
110+
111+
value.${propertyAccessor} = ${toValue};
112+
`;
113+
}
114+
99115
return `if(jo["${prop.unconstrainedPropertyName}"] != null) {
100116
value.${propertyAccessor} = ${toValue};
101117
}`;
@@ -151,15 +167,15 @@ function renderDeserialize({
151167
export const CSHARP_NEWTONSOFT_SERIALIZER_PRESET: CSharpPreset<CSharpOptions> =
152168
{
153169
class: {
154-
self: ({ renderer, content, model }) => {
170+
self: ({ renderer, content, model, options }) => {
155171
renderer.dependencyManager.addDependency('using Newtonsoft.Json;');
156172
renderer.dependencyManager.addDependency('using Newtonsoft.Json.Linq;');
157173
renderer.dependencyManager.addDependency(
158174
'using System.Collections.Generic;'
159175
);
160176
renderer.dependencyManager.addDependency('using System.Linq;');
161177

162-
const deserialize = renderDeserialize({ model });
178+
const deserialize = renderDeserialize({ model, options });
163179
const serialize = renderSerialize({ model });
164180

165181
return `[JsonConverter(typeof(${model.name}Converter))]

test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const doc = {
99
required: ['string prop'],
1010
properties: {
1111
'string prop': { type: 'string' },
12+
notRequiredStringProp: { type: 'string' },
1213
numberProp: { type: 'number' },
1314
enumProp: {
1415
$id: 'EnumTest',
@@ -29,7 +30,14 @@ const doc = {
2930
describe('Newtonsoft JSON serializer preset', () => {
3031
test('should render serialize and deserialize converters', async () => {
3132
const generator = new CSharpGenerator({
32-
presets: [CSHARP_NEWTONSOFT_SERIALIZER_PRESET]
33+
presets: [
34+
{
35+
preset: CSHARP_NEWTONSOFT_SERIALIZER_PRESET,
36+
options: {
37+
enforceRequired: true
38+
}
39+
}
40+
]
3341
});
3442

3543
const outputModels = await generator.generate(doc);

test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ exports[`Newtonsoft JSON serializer preset should render serialize and deseriali
55
public partial class Test
66
{
77
private string stringProp;
8+
private string? notRequiredStringProp;
89
private double? numberProp;
910
private EnumTest? enumProp;
1011
private NestedTest? objectProp;
@@ -16,6 +17,12 @@ public partial class Test
1617
set { this.stringProp = value; }
1718
}
1819
20+
public string? NotRequiredStringProp
21+
{
22+
get { return notRequiredStringProp; }
23+
set { this.notRequiredStringProp = value; }
24+
}
25+
1926
public double? NumberProp
2027
{
2128
get { return numberProp; }
@@ -48,8 +55,14 @@ public class TestConverter : JsonConverter<Test>
4855
JObject jo = JObject.Load(reader);
4956
Test value = new Test();
5057
51-
if(jo[\\"string prop\\"] != null) {
52-
value.StringProp = jo[\\"string prop\\"].ToObject<string>(serializer);
58+
if(jo[\\"string prop\\"] is null){
59+
throw new JsonSerializationException(\\"Required property 'string prop' is missing\\");
60+
}
61+
62+
value.StringProp = jo[\\"string prop\\"].ToObject<string>(serializer);
63+
64+
if(jo[\\"notRequiredStringProp\\"] != null) {
65+
value.NotRequiredStringProp = jo[\\"notRequiredStringProp\\"].ToObject<string?>(serializer);
5366
}
5467
if(jo[\\"numberProp\\"] != null) {
5568
value.NumberProp = jo[\\"numberProp\\"].ToObject<double?>(serializer);
@@ -61,7 +74,7 @@ if(jo[\\"objectProp\\"] != null) {
6174
value.ObjectProp = jo[\\"objectProp\\"].ToObject<NestedTest?>(serializer);
6275
}
6376
64-
var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"string prop\\" || prop.Name != \\"numberProp\\" || prop.Name != \\"enumProp\\" || prop.Name != \\"objectProp\\");
77+
var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"string prop\\" || prop.Name != \\"notRequiredStringProp\\" || prop.Name != \\"numberProp\\" || prop.Name != \\"enumProp\\" || prop.Name != \\"objectProp\\");
6578
value.AdditionalProperties = new Dictionary<string, dynamic>();
6679
6780
foreach (var additionalProperty in additionalProperties)
@@ -78,6 +91,10 @@ if(jo[\\"objectProp\\"] != null) {
7891
{
7992
jo.Add(\\"string prop\\", JToken.FromObject(value.StringProp, serializer));
8093
}
94+
if (value.NotRequiredStringProp != null)
95+
{
96+
jo.Add(\\"notRequiredStringProp\\", JToken.FromObject(value.NotRequiredStringProp, serializer));
97+
}
8198
if (value.NumberProp != null)
8299
{
83100
jo.Add(\\"numberProp\\", JToken.FromObject(value.NumberProp, serializer));

0 commit comments

Comments
 (0)