From 7ceb98082b770bdf2613a128c8f1124407d54f06 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Wed, 9 Nov 2022 11:04:28 +0100 Subject: [PATCH] feat: add C# Newtonsoft preset (#970) --- docs/languages/Csharp.md | 11 +- examples/README.md | 3 +- .../README.md | 0 .../__snapshots__/index.spec.ts.snap | 0 .../index.spec.ts | 0 .../index.ts | 0 .../package-lock.json | 0 .../package.json | 2 +- .../README.md | 17 ++ .../__snapshots__/index.spec.ts.snap | 48 ++++ .../index.spec.ts | 14 ++ .../index.ts | 29 +++ .../package-lock.json | 10 + .../package.json | 12 + package.json | 3 +- .../presets/NewtonsoftSerializerPreset.ts | 135 +++++++++++ src/generators/csharp/presets/index.ts | 1 + .../NewtonsoftSerializerPreset.spec.ts | 33 +++ .../NewtonsoftSerializerPreset.spec.ts.snap | 218 ++++++++++++++++++ 19 files changed, 532 insertions(+), 4 deletions(-) rename examples/{csharp-generate-serializer => csharp-generate-json-serializer}/README.md (100%) rename examples/{csharp-generate-serializer => csharp-generate-json-serializer}/__snapshots__/index.spec.ts.snap (100%) rename examples/{csharp-generate-serializer => csharp-generate-json-serializer}/index.spec.ts (100%) rename examples/{csharp-generate-serializer => csharp-generate-json-serializer}/index.ts (100%) rename examples/{csharp-generate-serializer => csharp-generate-json-serializer}/package-lock.json (100%) rename examples/{csharp-generate-serializer => csharp-generate-json-serializer}/package.json (91%) create mode 100644 examples/csharp-generate-newtonsoft-serializer/README.md create mode 100644 examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap create mode 100644 examples/csharp-generate-newtonsoft-serializer/index.spec.ts create mode 100644 examples/csharp-generate-newtonsoft-serializer/index.ts create mode 100644 examples/csharp-generate-newtonsoft-serializer/package-lock.json create mode 100644 examples/csharp-generate-newtonsoft-serializer/package.json create mode 100644 src/generators/csharp/presets/NewtonsoftSerializerPreset.ts create mode 100644 test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts create mode 100644 test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap diff --git a/docs/languages/Csharp.md b/docs/languages/Csharp.md index 3b4b12fb61..1335f2ba76 100644 --- a/docs/languages/Csharp.md +++ b/docs/languages/Csharp.md @@ -36,11 +36,20 @@ Here are all the supported presets and the libraries they use: To include functionality that convert the models using the [System.Text.Json](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/), to use this, use the preset `CSHARP_JSON_SERIALIZER_PRESET`. -Check out this [example for a live demonstration](../../examples/csharp-generate-serializer). +Check out this [example for a live demonstration](../../examples/csharp-generate-json-serializer). **External dependencies** Requires [System.Text.Json](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/), [System.Text.Json.Serialization](https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-6-0) and [System.Text.RegularExpressions](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions?view=net-6.0) to work. +#### 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`. + +Check out this [example for a live demonstration](../../examples/csharp-generate-newtonsoft-serializer). + +**External dependencies** +Requires [`Newtonsoft.Json`, `Newtonsoft.Json.Linq`](https://www.newtonsoft.com/json) and [System.Collections.Generic](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic?view=net-7.0). + ### To and from XML Currently not supported, [let everyone know you need it](https://github.com/asyncapi/modelina/issues/new?assignees=&labels=enhancement&template=enhancement.md)! diff --git a/examples/README.md b/examples/README.md index 7a984c34f6..857740e2a2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,7 +24,8 @@ This directory contains a series of self-contained examples that you can use as - [TEMPLATE](./TEMPLATE) - A basic template used to create new examples. - [java-generate-tostring](./java-generate-tostring) - A basic example that shows how to generate models that overwrite the `toString` method - [csharp-generate-equals-and-hashcode](./csharp-generate-equals-and-hashcode) - A basic example on how to generate models that overwrite the `Equal` and `GetHashCode` methods -- [csharp-generate-serializer](./csharp-generate-serializer) - A basic example on how to generate models that include function to serialize the data models to JSON +- [csharp-generate-json-serializer](./csharp-generate-json-serializer) - A basic example on how to generate models that include function to serialize the data models to and from JSON with System.Text.Json. +- [csharp-generate-newtonsoft-serializer](./csharp-generate-newtonsoft-serializer) - A basic example on how to generate models that include function to serialize the data models to and form JSON with Newtonsoft. - [csharp-overwrite-enum-naming](./csharp-overwrite-enum-naming) - A basic example on how to generate enum value names. - [csharp-use-inheritance](./csharp-use-inheritance) - A basic example that shows how to introduce inheritance to classes - [generate-javascript-models](./generate-javascript-models) - A basic example to generate JavaScript data models diff --git a/examples/csharp-generate-serializer/README.md b/examples/csharp-generate-json-serializer/README.md similarity index 100% rename from examples/csharp-generate-serializer/README.md rename to examples/csharp-generate-json-serializer/README.md diff --git a/examples/csharp-generate-serializer/__snapshots__/index.spec.ts.snap b/examples/csharp-generate-json-serializer/__snapshots__/index.spec.ts.snap similarity index 100% rename from examples/csharp-generate-serializer/__snapshots__/index.spec.ts.snap rename to examples/csharp-generate-json-serializer/__snapshots__/index.spec.ts.snap diff --git a/examples/csharp-generate-serializer/index.spec.ts b/examples/csharp-generate-json-serializer/index.spec.ts similarity index 100% rename from examples/csharp-generate-serializer/index.spec.ts rename to examples/csharp-generate-json-serializer/index.spec.ts diff --git a/examples/csharp-generate-serializer/index.ts b/examples/csharp-generate-json-serializer/index.ts similarity index 100% rename from examples/csharp-generate-serializer/index.ts rename to examples/csharp-generate-json-serializer/index.ts diff --git a/examples/csharp-generate-serializer/package-lock.json b/examples/csharp-generate-json-serializer/package-lock.json similarity index 100% rename from examples/csharp-generate-serializer/package-lock.json rename to examples/csharp-generate-json-serializer/package-lock.json diff --git a/examples/csharp-generate-serializer/package.json b/examples/csharp-generate-json-serializer/package.json similarity index 91% rename from examples/csharp-generate-serializer/package.json rename to examples/csharp-generate-json-serializer/package.json index b11ff1f3c6..6e2b7d90c5 100644 --- a/examples/csharp-generate-serializer/package.json +++ b/examples/csharp-generate-json-serializer/package.json @@ -1,6 +1,6 @@ { "config" : { - "example_name" : "csharp-generate-serializer" + "example_name" : "csharp-generate-json-serializer" }, "scripts": { "install": "cd ../.. && npm i", diff --git a/examples/csharp-generate-newtonsoft-serializer/README.md b/examples/csharp-generate-newtonsoft-serializer/README.md new file mode 100644 index 0000000000..0e1f200a54 --- /dev/null +++ b/examples/csharp-generate-newtonsoft-serializer/README.md @@ -0,0 +1,17 @@ +# C# Generate serializer functions for Newtonsoft + +A basic example of how to generate models and which includes a way to serialize them into and from JSON using Newtonsoft. + +## How to run this example + +Run this example using: + +```sh +npm i && npm run start +``` + +If you are on Windows, use the `start:windows` script instead: + +```sh +npm i && npm run start:windows +``` diff --git a/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap b/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000..27a2f37771 --- /dev/null +++ b/examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should be able to generate a model with functions to serialize the data model into JSON and should log expected output to console 1`] = ` +Array [ + "[JsonConverter(typeof(RootConverter))] +public class Root +{ + private string email; + + public string Email + { + get { return email; } + set { email = value; } + } +} + +public class RootConverter : JsonConverter +{ + public override Root ReadJson(JsonReader reader, Type objectType, Root existingValue, bool hasExistingValue, JsonSerializer serializer) +{ + JObject jo = JObject.Load(reader); + Root value = new Root(); + + if(jo[\\"email\\" != null) { + value.Email = jo[\\"email\\"].ToObject(serializer); +} + + + return value; +} + public override void WriteJson(JsonWriter writer, Root value, JsonSerializer serializer) +{ + JObject jo = new JObject(); + + if (value.email != null) +{ + jo.Add(\\"email\\", JToken.FromObject(value.Email, serializer)); +} + + + jo.WriteTo(writer); +} + + public override bool CanRead => true; + public override bool CanWrite => true; +}", +] +`; diff --git a/examples/csharp-generate-newtonsoft-serializer/index.spec.ts b/examples/csharp-generate-newtonsoft-serializer/index.spec.ts new file mode 100644 index 0000000000..534a2241e1 --- /dev/null +++ b/examples/csharp-generate-newtonsoft-serializer/index.spec.ts @@ -0,0 +1,14 @@ +const spy = jest.spyOn(global.console, 'log').mockImplementation(() => { return; }); +import {generate} from './index'; + +describe('Should be able to generate a model with functions to serialize the data model into JSON ', () => { + afterAll(() => { + jest.restoreAllMocks(); + }); + + test('and should log expected output to console', async () => { + await generate(); + expect(spy.mock.calls.length).toEqual(1); + expect(spy.mock.calls[0]).toMatchSnapshot(); + }); +}); diff --git a/examples/csharp-generate-newtonsoft-serializer/index.ts b/examples/csharp-generate-newtonsoft-serializer/index.ts new file mode 100644 index 0000000000..50c59d2b25 --- /dev/null +++ b/examples/csharp-generate-newtonsoft-serializer/index.ts @@ -0,0 +1,29 @@ +import { CSharpGenerator, CSHARP_NEWTONSOFT_SERIALIZER_PRESET } from '../../src'; + +const generator = new CSharpGenerator({ + presets: [ + CSHARP_NEWTONSOFT_SERIALIZER_PRESET + ] +}); + +const jsonSchemaDraft7 = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + additionalProperties: false, + properties: { + email: { + type: 'string', + format: 'email' + } + } +}; + +export async function generate() : Promise { + const models = await generator.generate(jsonSchemaDraft7); + for (const model of models) { + console.log(model.result); + } +} +if (require.main === module) { + generate(); +} diff --git a/examples/csharp-generate-newtonsoft-serializer/package-lock.json b/examples/csharp-generate-newtonsoft-serializer/package-lock.json new file mode 100644 index 0000000000..98c488824f --- /dev/null +++ b/examples/csharp-generate-newtonsoft-serializer/package-lock.json @@ -0,0 +1,10 @@ +{ + "name": "csharp-generate-serializer", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "hasInstallScript": true + } + } +} diff --git a/examples/csharp-generate-newtonsoft-serializer/package.json b/examples/csharp-generate-newtonsoft-serializer/package.json new file mode 100644 index 0000000000..bf39bf4c54 --- /dev/null +++ b/examples/csharp-generate-newtonsoft-serializer/package.json @@ -0,0 +1,12 @@ +{ + "config" : { + "example_name" : "csharp-generate-newtonsoft-serializer" + }, + "scripts": { + "install": "cd ../.. && npm i", + "start": "../../node_modules/.bin/ts-node --cwd ../../ ./examples/$npm_package_config_example_name/index.ts", + "start:windows": "..\\..\\node_modules\\.bin\\ts-node --cwd ..\\..\\ .\\examples\\%npm_package_config_example_name%\\index.ts", + "test": "../../node_modules/.bin/jest --config=../../jest.config.js ./examples/$npm_package_config_example_name/index.spec.ts", + "test:windows": "..\\..\\node_modules\\.bin\\jest --config=..\\..\\jest.config.js examples/%npm_package_config_example_name%/index.spec.ts" + } +} diff --git a/package.json b/package.json index a5d7d928c8..ce28b42b85 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,8 @@ "docker:test:blackbox": "npm run docker:build && docker run asyncapi/modelina npm run test:blackbox", "test": "npm run test:library && npm run test:examples", "test:library": "cross-env CI=true jest --coverage --testPathIgnorePatterns ./test/blackbox --testPathIgnorePatterns ./examples", - "test:examples": "cross-env CI=true jest ./examples --testPathIgnorePatterns ./examples/integrate-with-react && npm run test:examples:websites", + "test:examples": "npm run test:examples:regular && npm run test:examples:websites", + "test:examples:regular": "cross-env CI=true jest ./examples --testPathIgnorePatterns ./examples/integrate-with-react", "test:examples:websites": "cd ./examples/integrate-with-react && npm i && npm run test", "test:blackbox": "cross-env CI=true jest ./test/blackbox", "test:watch": "jest --watch", diff --git a/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts b/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts new file mode 100644 index 0000000000..a47c9068c9 --- /dev/null +++ b/src/generators/csharp/presets/NewtonsoftSerializerPreset.ts @@ -0,0 +1,135 @@ +import { CSharpPreset } from '../CSharpPreset'; +import { ConstrainedDictionaryModel, ConstrainedEnumModel, ConstrainedObjectModel, ConstrainedReferenceModel } from '../../../models'; +import { CSharpOptions } from '../CSharpGenerator'; +import { pascalCase } from 'change-case'; + +/** + * Render `serialize` function based on model + */ +function renderSerialize({ model }: { + model: ConstrainedObjectModel +}): string { + const corePropsWrite = Object.values(model.properties) + .filter((prop) => !(prop.property instanceof ConstrainedDictionaryModel) || prop.property.serializationType === 'normal') + .map((prop) => { + const propertyAccessor = pascalCase(prop.propertyName); + let toJson = `jo.Add("${prop.unconstrainedPropertyName}", JToken.FromObject(value.${propertyAccessor}, serializer));`; + if (prop.property instanceof ConstrainedReferenceModel + && prop.property.ref instanceof ConstrainedEnumModel) { + toJson = `var enumValue = ${prop.property.type}Extensions.GetValue((${prop.property.type})value.${propertyAccessor}); +var stringEnumValue = enumValue.ToString(); +// C# converts booleans to uppercase True and False, which newtonsoft cannot understand +var jsonStringCompliant = stringEnumValue == "True" || stringEnumValue == "False" ? stringEnumValue.ToLower() : stringEnumValue; +var jsonToken = JToken.Parse(jsonStringCompliant); +jo.Add("${prop.unconstrainedPropertyName}", jsonToken);`; + } + return `if (value.${prop.propertyName} != null) +{ + ${toJson} +}`; + }); + const unwrapPropsWrite = Object.values(model.properties) + .filter((prop) => prop.property instanceof ConstrainedDictionaryModel && prop.property.serializationType === 'unwrap') + .map((prop) => { + const propertyAccessor = pascalCase(prop.propertyName); + return `if (value.${propertyAccessor} != null) + { + foreach (var unwrapProperty in value.${propertyAccessor}) + { + var hasProp = jo[unwrapProperty.Key]; + if (hasProp != null) continue; + jo.Add(unwrapProperty.Key, JToken.FromObject(unwrapProperty.Value, serializer)); + } +}`; + }); + return `public override void WriteJson(JsonWriter writer, ${model.name} value, JsonSerializer serializer) +{ + JObject jo = new JObject(); + + ${corePropsWrite.join('\n')} + ${unwrapPropsWrite.join('\n')} + + jo.WriteTo(writer); +}`; +} + +/** + * Render `deserialize` function based on model + */ +function renderDeserialize({ model }: { + model: ConstrainedObjectModel +}): string { + const unwrapDictionaryProps = Object.values(model.properties) + .filter((prop) => prop.property instanceof ConstrainedDictionaryModel && prop.property.serializationType === 'unwrap'); + const coreProps = Object.values(model.properties) + .filter((prop) => !(prop.property instanceof ConstrainedDictionaryModel) || prop.property.serializationType === 'normal'); + const corePropsRead = coreProps.map((prop) => { + const propertyAccessor = pascalCase(prop.propertyName); + let toValue = `jo["${prop.unconstrainedPropertyName}"].ToObject<${prop.property.type}>(serializer)`; + if (prop.property instanceof ConstrainedReferenceModel + && prop.property.ref instanceof ConstrainedEnumModel) { + toValue = `${prop.property.type}Extensions.To${prop.property.type}(jo["${prop.unconstrainedPropertyName}"])`; + } + return `if(jo["${prop.unconstrainedPropertyName}" != null) { + value.${propertyAccessor} = ${toValue}; +}`; + }); + const nonDictionaryPropCheck = coreProps.map((prop) => { + return `prop.Name != "${prop.unconstrainedPropertyName}"`; + }); + const dictionaryInitializers = unwrapDictionaryProps.map((prop) => { + const propertyAccessor = pascalCase(prop.propertyName); + return `value.${propertyAccessor} = new Dictionary<${(prop.property as ConstrainedDictionaryModel).key.type}, ${(prop.property as ConstrainedDictionaryModel).value.type}>();`; + }); + const unwrapDictionaryRead = unwrapDictionaryProps.map((prop) => { + const propertyAccessor = pascalCase(prop.propertyName); + return `value.${propertyAccessor}[additionalProperty.Name] = additionalProperty.Value.ToObject<${(prop.property as ConstrainedDictionaryModel).value.type}>(serializer);`; + }); + const additionalPropertiesCode = unwrapDictionaryProps.length !== 0 ? `var additionalProperties = jo.Properties().Where((prop) => ${nonDictionaryPropCheck.join(' || ')}); + ${dictionaryInitializers} + + foreach (var additionalProperty in additionalProperties) + { + ${unwrapDictionaryRead.join('\n')} + }` : ''; + return `public override ${model.name} ReadJson(JsonReader reader, Type objectType, ${model.name} existingValue, bool hasExistingValue, JsonSerializer serializer) +{ + JObject jo = JObject.Load(reader); + ${model.name} value = new ${model.name}(); + + ${corePropsRead.join('\n')} + + ${additionalPropertiesCode} + return value; +}`; +} + +/** + * Preset which adds Newtonsoft/JSON.net converters for serializing and deserializing the data models + * + * @implements {CSharpPreset} + */ +export const CSHARP_NEWTONSOFT_SERIALIZER_PRESET: CSharpPreset = { + class: { + self: ({ renderer, content, model }) => { + renderer.addDependency('using Newtonsoft.Json;'); + renderer.addDependency('using Newtonsoft.Json.Linq;'); + renderer.addDependency('using System.Collections.Generic;'); + + const deserialize = renderDeserialize({ model }); + const serialize = renderSerialize({ model }); + + return `[JsonConverter(typeof(${model.name}Converter))] +${content} + +public class ${model.name}Converter : JsonConverter<${model.name}> +{ + ${deserialize} + ${serialize} + + public override bool CanRead => true; + public override bool CanWrite => true; +}`; + }, + }, +}; diff --git a/src/generators/csharp/presets/index.ts b/src/generators/csharp/presets/index.ts index c7343600e4..04605832ae 100644 --- a/src/generators/csharp/presets/index.ts +++ b/src/generators/csharp/presets/index.ts @@ -1,2 +1,3 @@ export * from './JsonSerializerPreset'; +export * from './NewtonsoftSerializerPreset'; export * from './CommonPreset'; diff --git a/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts b/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts new file mode 100644 index 0000000000..90bbd2f325 --- /dev/null +++ b/test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts @@ -0,0 +1,33 @@ +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', () => { + test('should render serialize and deserialize converters', async () => { + const generator = new CSharpGenerator({ + presets: [ + CSHARP_NEWTONSOFT_SERIALIZER_PRESET + ] + }); + + 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(); + }); +}); diff --git a/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap b/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap new file mode 100644 index 0000000000..8395e7c9a9 --- /dev/null +++ b/test/generators/csharp/presets/__snapshots__/NewtonsoftSerializerPreset.spec.ts.snap @@ -0,0 +1,218 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Newtonsoft JSON serializer preset should render serialize and deserialize converters 1`] = ` +"[JsonConverter(typeof(TestConverter))] +public class Test +{ + private string stringProp; + private double numberProp; + private EnumTest enumProp; + private NestedTest objectProp; + private Dictionary additionalProperties; + + public string StringProp + { + get { return stringProp; } + set { stringProp = value; } + } + + public double NumberProp + { + get { return numberProp; } + set { numberProp = value; } + } + + public EnumTest EnumProp + { + get { return enumProp; } + set { enumProp = value; } + } + + public NestedTest ObjectProp + { + get { return objectProp; } + set { objectProp = value; } + } + + public Dictionary AdditionalProperties + { + get { return additionalProperties; } + set { additionalProperties = value; } + } +} + +public class TestConverter : JsonConverter +{ + public override Test ReadJson(JsonReader reader, Type objectType, Test existingValue, bool hasExistingValue, JsonSerializer serializer) +{ + JObject jo = JObject.Load(reader); + Test value = new Test(); + + if(jo[\\"string prop\\" != null) { + value.StringProp = jo[\\"string prop\\"].ToObject(serializer); +} +if(jo[\\"numberProp\\" != null) { + value.NumberProp = jo[\\"numberProp\\"].ToObject(serializer); +} +if(jo[\\"enumProp\\" != null) { + value.EnumProp = EnumTestExtensions.ToEnumTest(jo[\\"enumProp\\"]); +} +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\\"); + value.AdditionalProperties = new Dictionary(); + + foreach (var additionalProperty in additionalProperties) + { + value.AdditionalProperties[additionalProperty.Name] = additionalProperty.Value.ToObject(serializer); + } + return value; +} + public override void WriteJson(JsonWriter writer, Test value, JsonSerializer serializer) +{ + JObject jo = new JObject(); + + if (value.stringProp != null) +{ + jo.Add(\\"string prop\\", JToken.FromObject(value.StringProp, serializer)); +} +if (value.numberProp != null) +{ + jo.Add(\\"numberProp\\", JToken.FromObject(value.NumberProp, serializer)); +} +if (value.enumProp != null) +{ + var enumValue = EnumTestExtensions.GetValue((EnumTest)value.EnumProp); +var stringEnumValue = enumValue.ToString(); +// C# converts booleans to uppercase True and False, which newtonsoft cannot understand +var jsonStringCompliant = stringEnumValue == \\"True\\" || stringEnumValue == \\"False\\" ? stringEnumValue.ToLower() : stringEnumValue; +var jsonToken = JToken.Parse(jsonStringCompliant); +jo.Add(\\"enumProp\\", jsonToken); +} +if (value.objectProp != null) +{ + jo.Add(\\"objectProp\\", JToken.FromObject(value.ObjectProp, serializer)); +} + if (value.AdditionalProperties != null) + { + foreach (var unwrapProperty in value.AdditionalProperties) + { + var hasProp = jo[unwrapProperty.Key]; + if (hasProp != null) continue; + jo.Add(unwrapProperty.Key, JToken.FromObject(unwrapProperty.Value, serializer)); + } +} + + jo.WriteTo(writer); +} + + public override bool CanRead => true; + public override bool CanWrite => true; +}" +`; + +exports[`Newtonsoft JSON serializer preset should render serialize and deserialize converters 2`] = ` +"public enum EnumTest +{ + SOME_SPACE_ENUM_SPACE_STRING, + RESERVED_TRUE, + CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT, + NUMBER_2 +} + +public static class EnumTestExtensions +{ + public static dynamic GetValue(this EnumTest enumValue) + { + switch (enumValue) + { + case EnumTest.SOME_SPACE_ENUM_SPACE_STRING: return \\"Some enum String\\"; + case EnumTest.RESERVED_TRUE: return true; + case EnumTest.CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT: return \\"{\\\\\\"test\\\\\\":\\\\\\"test\\\\\\"}\\"; + case EnumTest.NUMBER_2: return 2; + } + return null; + } + + public static EnumTest? ToEnumTest(dynamic value) + { + switch (value) + { + case \\"Some enum String\\": return EnumTest.SOME_SPACE_ENUM_SPACE_STRING; + case true: return EnumTest.RESERVED_TRUE; + case \\"{\\\\\\"test\\\\\\":\\\\\\"test\\\\\\"}\\": return EnumTest.CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT; + case 2: return EnumTest.NUMBER_2; + } + return null; + } +} +" +`; + +exports[`Newtonsoft JSON serializer preset should render serialize and deserialize converters 3`] = ` +"[JsonConverter(typeof(NestedTestConverter))] +public class NestedTest +{ + private string stringProp; + private Dictionary additionalProperties; + + public string StringProp + { + get { return stringProp; } + set { stringProp = value; } + } + + public Dictionary AdditionalProperties + { + get { return additionalProperties; } + set { additionalProperties = value; } + } +} + +public class NestedTestConverter : JsonConverter +{ + public override NestedTest ReadJson(JsonReader reader, Type objectType, NestedTest existingValue, bool hasExistingValue, JsonSerializer serializer) +{ + JObject jo = JObject.Load(reader); + NestedTest value = new NestedTest(); + + if(jo[\\"stringProp\\" != null) { + value.StringProp = jo[\\"stringProp\\"].ToObject(serializer); +} + + var additionalProperties = jo.Properties().Where((prop) => prop.Name != \\"stringProp\\"); + value.AdditionalProperties = new Dictionary(); + + foreach (var additionalProperty in additionalProperties) + { + value.AdditionalProperties[additionalProperty.Name] = additionalProperty.Value.ToObject(serializer); + } + return value; +} + public override void WriteJson(JsonWriter writer, NestedTest value, JsonSerializer serializer) +{ + JObject jo = new JObject(); + + if (value.stringProp != null) +{ + jo.Add(\\"stringProp\\", JToken.FromObject(value.StringProp, serializer)); +} + if (value.AdditionalProperties != null) + { + foreach (var unwrapProperty in value.AdditionalProperties) + { + var hasProp = jo[unwrapProperty.Key]; + if (hasProp != null) continue; + jo.Add(unwrapProperty.Key, JToken.FromObject(unwrapProperty.Value, serializer)); + } +} + + jo.WriteTo(writer); +} + + public override bool CanRead => true; + public override bool CanWrite => true; +}" +`;