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

fix: un/marshal TS class functions did not render references correctly #337

Merged
Merged
Show file tree
Hide file tree
Changes from 5 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
105 changes: 67 additions & 38 deletions src/generators/typescript/presets/CommonPreset.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TypeScriptRenderer } from '../TypeScriptRenderer';
import { TypeScriptPreset } from '../TypeScriptPreset';
import { getUniquePropertyName, DefaultPropertyNames } from '../../../helpers';
import { CommonModel } from '../../../models';
import { getUniquePropertyName, DefaultPropertyNames, TypeHelpers, ModelKind } from '../../../helpers';
import { CommonInputModel, CommonModel } from '../../../models';
import renderExampleFunction from './utils/ExampleFunction';

export interface TypeScriptCommonPresetOptions {
Expand All @@ -12,55 +12,66 @@ export interface TypeScriptCommonPresetOptions {
function realizePropertyFactory(prop: string) {
return `$\{typeof ${prop} === 'number' || typeof ${prop} === 'boolean' ? ${prop} : JSON.stringify(${prop})}`;
}

function renderMarshalProperties(model: CommonModel, renderer: TypeScriptRenderer) {
function renderMarshalProperty(modelInstanceVariable: string, model: CommonModel, inputModel: CommonInputModel) {
if (model.$ref) {
const resolvedModel = inputModel.models[model.$ref];
const propertyModelKind = TypeHelpers.extractKind(resolvedModel);
//Referenced enums only need standard marshalling, so lets filter those away
if (propertyModelKind !== ModelKind.ENUM) {
return `$\{${modelInstanceVariable}.marshal()}`;
}
}
return realizePropertyFactory(modelInstanceVariable);
}
function renderMarshalProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) {
const properties = model.properties || {};
const propertyKeys = [...Object.entries(properties)];
const marshalProperties = propertyKeys.map(([prop, propModel]) => {
const formattedPropertyName = renderer.nameProperty(prop, propModel);
const modelInstanceVariable = `this.${formattedPropertyName}`;
const propMarshalReference = `json += \`"${prop}": $\{this.${formattedPropertyName}.marshal()},\`;`;
const propMarshal = `json += \`"${prop}": ${realizePropertyFactory(modelInstanceVariable)},\`;`;
const propMarshalCode = propModel.$ref !== undefined ? propMarshalReference : propMarshal;
return `if(this.${formattedPropertyName} !== undefined) {
${propMarshalCode}
const propMarshalCode = renderMarshalProperty(modelInstanceVariable, propModel, inputModel);
const marshalCode = `json += \`"${prop}": ${propMarshalCode},\`;`;
return `if(${modelInstanceVariable} !== undefined) {
${marshalCode}
}`;
});
return marshalProperties.join('\n');
}

function renderMarshalPatternProperties(model: CommonModel, renderer: TypeScriptRenderer) {
function renderMarshalPatternProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) {
let marshalPatternProperties = '';
if (model.patternProperties !== undefined) {
for (const [pattern, patternModel] of Object.entries(model.patternProperties)) {
let patternPropertyName = getUniquePropertyName(model, `${pattern}${DefaultPropertyNames.patternProperties}`);
patternPropertyName = renderer.nameProperty(patternPropertyName, patternModel);
const patternPropertyMarshalReference = 'json += `"${key}": ${value.marshal()},`;';
const patternPropertyMarshal = `json += \`"$\{key}": ${realizePropertyFactory('value')},\`;`;
const modelInstanceVariable = 'value';
const patternPropertyMarshalCode = renderMarshalProperty(modelInstanceVariable, patternModel, inputModel);
const marshalCode = `json += \`"$\{key}": ${patternPropertyMarshalCode},\`;`;
marshalPatternProperties += `if(this.${patternPropertyName} !== undefined) {
for (const [key, value] of this.${patternPropertyName}.entries()) {
//Only render pattern properties which are not already a property
if(Object.keys(this).includes(String(key))) continue;
${patternModel.$ref !== undefined ? patternPropertyMarshalReference : patternPropertyMarshal}
${marshalCode}
}
}`;
}
}
return marshalPatternProperties;
}

function renderMarshalAdditionalProperties(model: CommonModel, renderer: TypeScriptRenderer) {
function renderMarshalAdditionalProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) {
let marshalAdditionalProperties = '';
if (model.additionalProperties !== undefined) {
let additionalPropertyName = getUniquePropertyName(model, DefaultPropertyNames.additionalProperties);
additionalPropertyName = renderer.nameProperty(additionalPropertyName, model.additionalProperties);
const additionalPropertyMarshalReference = 'json += `"${key}": ${value.marshal()},`;';
const additionalPropertyMarshal = `json += \`"$\{key}": ${realizePropertyFactory('value')},\`;`;
const modelInstanceVariable = 'value';
const patternPropertyMarshalCode = renderMarshalProperty(modelInstanceVariable, model.additionalProperties, inputModel);
const marshalCode = `json += \`"$\{key}": ${patternPropertyMarshalCode},\`;`;
marshalAdditionalProperties = `if(this.${additionalPropertyName} !== undefined) {
for (const [key, value] of this.${additionalPropertyName}.entries()) {
//Only render additionalProperties which are not already a property
if(Object.keys(this).includes(String(key))) continue;
${model.additionalProperties.$ref !== undefined ? additionalPropertyMarshalReference : additionalPropertyMarshal}
${marshalCode}
}
}`;
}
Expand All @@ -70,82 +81,100 @@ function renderMarshalAdditionalProperties(model: CommonModel, renderer: TypeScr
/**
* Render `marshal` function based on model
*/
function renderMarshal({ renderer, model }: {
function renderMarshal({ renderer, model, inputModel }: {
renderer: TypeScriptRenderer,
model: CommonModel,
inputModel: CommonInputModel
}): string {
return `public marshal() : string {
let json = '{'
${renderer.indent(renderMarshalProperties(model, renderer))}
${renderer.indent(renderMarshalPatternProperties(model, renderer))}
${renderer.indent(renderMarshalAdditionalProperties(model, renderer))}
${renderer.indent(renderMarshalProperties(model, renderer, inputModel))}
${renderer.indent(renderMarshalPatternProperties(model, renderer, inputModel))}
${renderer.indent(renderMarshalAdditionalProperties(model, renderer, inputModel))}

//Remove potential last comma
return \`$\{json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`;
}`;
}

function renderUnmarshalProperties(model: CommonModel, renderer: TypeScriptRenderer) {
function renderUnmarshalProperty(modelInstanceVariable: string, model: CommonModel, inputModel: CommonInputModel, renderer: TypeScriptRenderer) {
if (model.$ref) {
const resolvedModel = inputModel.models[model.$ref];
const propertyModelKind = TypeHelpers.extractKind(resolvedModel);
//Referenced enums only need standard marshalling, so lets filter those away
if (propertyModelKind !== ModelKind.ENUM) {
return `${renderer.nameType(model.$ref)}.unmarshal(${modelInstanceVariable})`;
}
}
return `${modelInstanceVariable}`;
}
function renderUnmarshalProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) {
const properties = model.properties || {};
const propertyKeys = [...Object.entries(properties)];
const unmarshalProperties = propertyKeys.map(([prop, propModel]) => {
const formattedPropertyName = renderer.nameProperty(prop, propModel);
const propUnmarshal = propModel.$ref !== undefined ? `${renderer.nameType(propModel.$ref)}.unmarshal(obj["${prop}"])` : `obj["${prop}"]`;
return `if (obj["${prop}"] !== undefined) {
instance.${formattedPropertyName} = ${propUnmarshal};
const modelInstanceVariable = `obj["${prop}"]`;
const unmarshalCode = renderUnmarshalProperty(modelInstanceVariable, propModel, inputModel, renderer);
return `if (${modelInstanceVariable} !== undefined) {
instance.${formattedPropertyName} = ${unmarshalCode};
}`;
});
return unmarshalProperties.join('\n');
}

function renderUnmarshalPatternProperties(model: CommonModel, renderer: TypeScriptRenderer) {
function renderUnmarshalPatternProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) {
let unmarshalPatternProperties = '';
let setPatternPropertiesMap = '';
if (model.patternProperties !== undefined) {
for (const [pattern, patternModel] of Object.entries(model.patternProperties)) {
let patternPropertyName = getUniquePropertyName(model, `${pattern}${DefaultPropertyNames.patternProperties}`);
patternPropertyName = renderer.nameProperty(patternPropertyName, patternModel);
setPatternPropertiesMap = `if (instance.${patternPropertyName} === undefined) {instance.${patternPropertyName} = new Map();}`;
const modelInstanceVariable = 'value as any';
const unmarshalCode = renderUnmarshalProperty(modelInstanceVariable, patternModel, inputModel, renderer);
setPatternPropertiesMap += `if (instance.${patternPropertyName} === undefined) {instance.${patternPropertyName} = new Map();}\n`;
unmarshalPatternProperties += `//Check all pattern properties
if (key.match(new RegExp('${pattern}'))) {
instance.${patternPropertyName}.set(key, value as any);
instance.${patternPropertyName}.set(key, ${unmarshalCode});
continue;
}`;
}
}
return {unmarshalPatternProperties, setPatternPropertiesMap};
}

function renderUnmarshalAdditionalProperties(model: CommonModel, renderer: TypeScriptRenderer) {
function renderUnmarshalAdditionalProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) {
let unmarshalAdditionalProperties = '';
let setAdditionalPropertiesMap = '';
if (model.additionalProperties !== undefined) {
let additionalPropertyName = getUniquePropertyName(model, DefaultPropertyNames.additionalProperties);
additionalPropertyName = renderer.nameProperty(additionalPropertyName, model.additionalProperties);
const additionalPropertiesCast = model.additionalProperties.$ref !== undefined ? `${renderer.nameType(model.$id)}.unmarshal(value)` : 'value as any';
const modelInstanceVariable = 'value as any';
const unmarshalCode = renderUnmarshalProperty(modelInstanceVariable, model.additionalProperties, inputModel, renderer);
setAdditionalPropertiesMap = `if (instance.${additionalPropertyName} === undefined) {instance.${additionalPropertyName} = new Map();}`;
unmarshalAdditionalProperties = `instance.${additionalPropertyName}.set(key, ${additionalPropertiesCast});`;
unmarshalAdditionalProperties = `instance.${additionalPropertyName}.set(key, ${unmarshalCode});`;
}
return {unmarshalAdditionalProperties, setAdditionalPropertiesMap};
}

/**
* Render `unmarshal` function based on model
*/
function renderUnmarshal({ renderer, model }: {
function renderUnmarshal({ renderer, model, inputModel }: {
renderer: TypeScriptRenderer,
model: CommonModel,
inputModel: CommonInputModel
}): string {
const properties = model.properties || {};
const {unmarshalPatternProperties, setPatternPropertiesMap} = renderUnmarshalPatternProperties(model, renderer);
const {unmarshalAdditionalProperties, setAdditionalPropertiesMap} = renderUnmarshalAdditionalProperties(model, renderer);
const {unmarshalPatternProperties, setPatternPropertiesMap} = renderUnmarshalPatternProperties(model, renderer, inputModel);
const {unmarshalAdditionalProperties, setAdditionalPropertiesMap} = renderUnmarshalAdditionalProperties(model, renderer, inputModel);
const unmarshalProperties = renderUnmarshalProperties(model, renderer, inputModel);
const formattedModelName = renderer.nameType(model.$id);
const propertyNames = Object.keys(properties).map((prop => `"${prop}"`));
return `public static unmarshal(json: string | object): ${formattedModelName} {
const obj = typeof json === "object" ? json : JSON.parse(json);
const instance = new ${formattedModelName}({} as any);

${renderer.indent(renderUnmarshalProperties(model, renderer))}
${renderer.indent(unmarshalProperties)}

//Not part of core properties
${setPatternPropertiesMap}
Expand All @@ -165,13 +194,13 @@ ${renderer.indent(unmarshalAdditionalProperties, 4)}
*/
export const TS_COMMON_PRESET: TypeScriptPreset = {
class: {
additionalContent({ renderer, model, content, options }) {
additionalContent({ renderer, model, content, options, inputModel }) {
options = options || {};
const blocks: string[] = [];

if (options.marshalling === true) {
blocks.push(renderMarshal({ renderer, model }));
blocks.push(renderUnmarshal({ renderer, model }));
blocks.push(renderMarshal({ renderer, model, inputModel }));
blocks.push(renderUnmarshal({ renderer, model, inputModel }));
}

if (options.example === true) {
Expand Down
2 changes: 1 addition & 1 deletion test/blackbox/Dummy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('Dummy JSON Schema file', () => {
const renderOutputPath = path.resolve(__dirname, './output/ts/class/output.ts');
await renderModels(generatedModels, renderOutputPath);
const transpiledOutputPath = path.resolve(__dirname, './output/ts/class/output.js');
const transpileAndRunCommand = `tsc -t es5 ${renderOutputPath} && node ${transpiledOutputPath}`;
const transpileAndRunCommand = `tsc --downlevelIteration -t es5 ${renderOutputPath} && node ${transpiledOutputPath}`;
await execCommand(transpileAndRunCommand);
});

Expand Down
Loading