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: #2114 - add required field management in csharp newtonsoft serializer #2117

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading