Skip to content

Commit

Permalink
feat: add required field management in csharp newtonsoft serializer (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
fr-th authored Jan 25, 2025
1 parent c91d64d commit 4db3328
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 13 deletions.
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
Expand Up @@ -9,6 +9,7 @@ const doc = {
required: ['string prop'],
properties: {
'string prop': { type: 'string' },
notRequiredStringProp: { type: 'string' },
numberProp: { type: 'number' },
enumProp: {
$id: 'EnumTest',
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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; }
Expand Down Expand Up @@ -48,8 +55,14 @@ public class TestConverter : JsonConverter<Test>
JObject jo = JObject.Load(reader);
Test value = new Test();
if(jo[\\"string prop\\"] != null) {
value.StringProp = jo[\\"string prop\\"].ToObject<string>(serializer);
if(jo[\\"string prop\\"] is null){
throw new JsonSerializationException(\\"Required property 'string prop' is missing\\");
}
value.StringProp = jo[\\"string prop\\"].ToObject<string>(serializer);
if(jo[\\"notRequiredStringProp\\"] != null) {
value.NotRequiredStringProp = jo[\\"notRequiredStringProp\\"].ToObject<string?>(serializer);
}
if(jo[\\"numberProp\\"] != null) {
value.NumberProp = jo[\\"numberProp\\"].ToObject<double?>(serializer);
Expand All @@ -61,7 +74,7 @@ if(jo[\\"objectProp\\"] != null) {
value.ObjectProp = jo[\\"objectProp\\"].ToObject<NestedTest?>(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<string, dynamic>();
foreach (var additionalProperty in additionalProperties)
Expand All @@ -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));
Expand Down

0 comments on commit 4db3328

Please sign in to comment.