Skip to content

Commit

Permalink
fix: un/marshal TS class functions did not render references correctly (
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Sep 2, 2021
1 parent 8a17451 commit 0885834
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 93 deletions.
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

0 comments on commit 0885834

Please sign in to comment.