-
-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add C# Newtonsoft preset (#970)
- Loading branch information
1 parent
dd613c3
commit 7ceb980
Showing
19 changed files
with
532 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion
2
...s/csharp-generate-serializer/package.json → ...arp-generate-json-serializer/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
``` |
48 changes: 48 additions & 0 deletions
48
examples/csharp-generate-newtonsoft-serializer/__snapshots__/index.spec.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Root> | ||
{ | ||
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<string>(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; | ||
}", | ||
] | ||
`; |
14 changes: 14 additions & 0 deletions
14
examples/csharp-generate-newtonsoft-serializer/index.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<void> { | ||
const models = await generator.generate(jsonSchemaDraft7); | ||
for (const model of models) { | ||
console.log(model.result); | ||
} | ||
} | ||
if (require.main === module) { | ||
generate(); | ||
} |
10 changes: 10 additions & 0 deletions
10
examples/csharp-generate-newtonsoft-serializer/package-lock.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
12 changes: 12 additions & 0 deletions
12
examples/csharp-generate-newtonsoft-serializer/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
src/generators/csharp/presets/NewtonsoftSerializerPreset.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<CSharpOptions> = { | ||
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; | ||
}`; | ||
}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './JsonSerializerPreset'; | ||
export * from './NewtonsoftSerializerPreset'; | ||
export * from './CommonPreset'; |
33 changes: 33 additions & 0 deletions
33
test/generators/csharp/presets/NewtonsoftSerializerPreset.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
}); | ||
}); |
Oops, something went wrong.