diff --git a/examples/typescript-generate-jsonbinpack/__snapshots__/index.spec.ts.snap b/examples/typescript-generate-jsonbinpack/__snapshots__/index.spec.ts.snap index 40553a144b..e16d71e525 100644 --- a/examples/typescript-generate-jsonbinpack/__snapshots__/index.spec.ts.snap +++ b/examples/typescript-generate-jsonbinpack/__snapshots__/index.spec.ts.snap @@ -17,10 +17,9 @@ Array [ public marshal() : string { let json = '{' if(this.email !== undefined) { - json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; + json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; } - - + //Remove potential last comma return \`\${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; } @@ -32,9 +31,8 @@ Array [ if (obj[\\"email\\"] !== undefined) { instance.email = obj[\\"email\\"]; } - - - + + return instance; } diff --git a/examples/typescript-generate-marshalling/__snapshots__/index.spec.ts.snap b/examples/typescript-generate-marshalling/__snapshots__/index.spec.ts.snap index 500812add9..d43de89024 100644 --- a/examples/typescript-generate-marshalling/__snapshots__/index.spec.ts.snap +++ b/examples/typescript-generate-marshalling/__snapshots__/index.spec.ts.snap @@ -17,10 +17,9 @@ Array [ public marshal() : string { let json = '{' if(this.email !== undefined) { - json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; + json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; } - - + //Remove potential last comma return \`\${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; } @@ -32,9 +31,8 @@ Array [ if (obj[\\"email\\"] !== undefined) { instance.email = obj[\\"email\\"]; } - - - + + return instance; } }", diff --git a/src/generators/typescript/presets/CommonPreset.ts b/src/generators/typescript/presets/CommonPreset.ts index bc1510dee4..5b44ebcb42 100644 --- a/src/generators/typescript/presets/CommonPreset.ts +++ b/src/generators/typescript/presets/CommonPreset.ts @@ -1,319 +1,13 @@ import { TypeScriptPreset } from '../TypeScriptPreset'; -import { - ConstrainedObjectModel, - ConstrainedDictionaryModel, - ConstrainedReferenceModel, - ConstrainedMetaModel, - ConstrainedEnumModel, - ConstrainedUnionModel, - ConstrainedArrayModel -} from '../../../models'; import renderExampleFunction from './utils/ExampleFunction'; -import { ClassRenderer } from '../renderers/ClassRenderer'; +import { renderUnmarshal } from './utils/UnmarshalFunction'; +import { renderMarshal } from './utils/MarshalFunction'; export interface TypeScriptCommonPresetOptions { marshalling: boolean; example: boolean; } -function realizePropertyFactory(prop: string) { - return `$\{typeof ${prop} === 'number' || typeof ${prop} === 'boolean' ? ${prop} : JSON.stringify(${prop})}`; -} - -function renderMarshalProperty( - modelInstanceVariable: string, - model: ConstrainedMetaModel -) { - if ( - model instanceof ConstrainedReferenceModel && - !(model.ref instanceof ConstrainedEnumModel) - ) { - return `$\{${modelInstanceVariable}.marshal()}`; - } - - return realizePropertyFactory(modelInstanceVariable); -} - -function renderUnionSerializationArray( - modelInstanceVariable: string, - prop: string, - unconstrainedProperty: string, - unionModel: ConstrainedUnionModel -) { - const propName = `${prop}JsonValues`; - const allUnionReferences = unionModel.union - .filter((model) => { - return ( - model instanceof ConstrainedReferenceModel && - !(model.ref instanceof ConstrainedEnumModel) - ); - }) - .map((model) => { - return `unionItem instanceof ${model.type}`; - }); - const allUnionReferencesCondition = allUnionReferences.join(' || '); - const hasUnionReference = allUnionReferences.length > 0; - let unionSerialization = `${propName}.push(typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem))`; - if (hasUnionReference) { - unionSerialization = `if(${allUnionReferencesCondition}) { - ${propName}.push(unionItem.marshal()); - } else { - ${propName}.push(typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem)) - }`; - } - return `let ${propName}: any[] = []; - for (const unionItem of ${modelInstanceVariable}) { - ${unionSerialization} - } - json += \`"${unconstrainedProperty}": [\${${propName}.join(',')}],\`;`; -} -function renderArraySerialization( - modelInstanceVariable: string, - prop: string, - unconstrainedProperty: string, - arrayModel: ConstrainedArrayModel -) { - const propName = `${prop}JsonValues`; - return `let ${propName}: any[] = []; - for (const unionItem of ${modelInstanceVariable}) { - ${propName}.push(\`${renderMarshalProperty( - 'unionItem', - arrayModel.valueModel - )}\`); - } - json += \`"${unconstrainedProperty}": [\${${propName}.join(',')}],\`;`; -} -function renderUnionSerialization( - modelInstanceVariable: string, - unconstrainedProperty: string, - unionModel: ConstrainedUnionModel -) { - const allUnionReferences = unionModel.union - .filter((model) => { - return ( - model instanceof ConstrainedReferenceModel && - !(model.ref instanceof ConstrainedEnumModel) - ); - }) - .map((model) => { - return `${modelInstanceVariable} instanceof ${model.type}`; - }); - const allUnionReferencesCondition = allUnionReferences.join(' || '); - const hasUnionReference = allUnionReferences.length > 0; - if (hasUnionReference) { - return `if(${allUnionReferencesCondition}) { - json += \`"${unconstrainedProperty}": $\{${modelInstanceVariable}.marshal()},\`; - } else { - json += \`"${unconstrainedProperty}": ${realizePropertyFactory( - modelInstanceVariable - )},\`; - }`; - } - return `json += \`"${unconstrainedProperty}": ${realizePropertyFactory( - modelInstanceVariable - )},\`;`; -} -function renderMarshalProperties(model: ConstrainedObjectModel) { - const properties = model.properties || {}; - const propertyKeys = [...Object.entries(properties)]; - - //These are a bit special as 'unwrap' dictionary models means they have to be unwrapped within the JSON object. - const unwrapDictionaryProperties = []; - const normalProperties = []; - for (const entry of propertyKeys) { - if ( - entry[1].property instanceof ConstrainedDictionaryModel && - entry[1].property.serializationType === 'unwrap' - ) { - unwrapDictionaryProperties.push(entry); - } else { - normalProperties.push(entry); - } - } - - const marshalNormalProperties = normalProperties.map(([prop, propModel]) => { - const modelInstanceVariable = `this.${prop}`; - let marshalCode = ''; - if ( - propModel.property instanceof ConstrainedArrayModel && - propModel.property.valueModel instanceof ConstrainedUnionModel - ) { - marshalCode = renderUnionSerializationArray( - modelInstanceVariable, - prop, - propModel.unconstrainedPropertyName, - propModel.property.valueModel - ); - } else if (propModel.property instanceof ConstrainedUnionModel) { - marshalCode = renderUnionSerialization( - modelInstanceVariable, - propModel.unconstrainedPropertyName, - propModel.property - ); - } else if (propModel.property instanceof ConstrainedArrayModel) { - marshalCode = renderArraySerialization( - modelInstanceVariable, - prop, - propModel.unconstrainedPropertyName, - propModel.property - ); - } else { - const propMarshalCode = renderMarshalProperty( - modelInstanceVariable, - propModel.property - ); - marshalCode = `json += \`"${propModel.unconstrainedPropertyName}": ${propMarshalCode},\`;`; - } - return `if(${modelInstanceVariable} !== undefined) { - ${marshalCode} -}`; - }); - - const marshalUnwrapDictionaryProperties = unwrapDictionaryProperties.map( - ([prop, propModel]) => { - const modelInstanceVariable = 'value'; - const patternPropertyMarshalCode = renderMarshalProperty( - modelInstanceVariable, - (propModel.property as ConstrainedDictionaryModel).value - ); - const marshalCode = `json += \`"$\{key}": ${patternPropertyMarshalCode},\`;`; - return `if(this.${prop} !== undefined) { -for (const [key, value] of this.${prop}.entries()) { - //Only unwrap those who are not already a property in the JSON object - if(Object.keys(this).includes(String(key))) continue; - ${marshalCode} - } -}`; - } - ); - - return ` -${marshalNormalProperties.join('\n')} -${marshalUnwrapDictionaryProperties.join('\n')} -`; -} - -/** - * Render `marshal` function based on model - */ -function renderMarshal({ - renderer, - model -}: { - renderer: ClassRenderer; - model: ConstrainedObjectModel; -}): string { - return `public marshal() : string { - let json = '{' -${renderer.indent(renderMarshalProperties(model))} - //Remove potential last comma - return \`$\{json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; -}`; -} - -function renderUnmarshalProperty( - modelInstanceVariable: string, - model: ConstrainedMetaModel -) { - if ( - model instanceof ConstrainedReferenceModel && - !(model.ref instanceof ConstrainedEnumModel) - ) { - return `${model.type}.unmarshal(${modelInstanceVariable})`; - } - return `${modelInstanceVariable}`; -} -function renderUnmarshalProperties(model: ConstrainedObjectModel) { - const properties = model.properties || {}; - const propertyKeys = [...Object.entries(properties)]; - const originalPropertyNames = propertyKeys.map(([, model]) => { - return model.unconstrainedPropertyName; - }); - //These are a bit special as 'unwrap' dictionary models means they have to be unwrapped within the JSON object. - const unwrapDictionaryProperties = []; - const normalProperties = []; - for (const entry of propertyKeys) { - // if const value exists, we don't need to unmarshal this property because it exist in the class/interface - if (entry[1].property.options.const) { - continue; - } - - if ( - entry[1].property instanceof ConstrainedDictionaryModel && - entry[1].property.serializationType === 'unwrap' - ) { - unwrapDictionaryProperties.push(entry); - } else { - normalProperties.push(entry); - } - } - - const unmarshalNormalProperties = normalProperties.map( - ([prop, propModel]) => { - const modelInstanceVariable = `obj["${propModel.unconstrainedPropertyName}"]`; - const unmarshalCode = renderUnmarshalProperty( - modelInstanceVariable, - propModel.property - ); - return `if (${modelInstanceVariable} !== undefined) { - instance.${prop} = ${unmarshalCode}; -}`; - } - ); - - const setDictionaryProperties = []; - const unmarshalDictionaryProperties = []; - for (const [prop, propModel] of unwrapDictionaryProperties) { - const modelInstanceVariable = 'value as any'; - const unmarshalCode = renderUnmarshalProperty( - modelInstanceVariable, - (propModel.property as ConstrainedDictionaryModel).value - ); - setDictionaryProperties.push( - `if (instance.${prop} === undefined) {instance.${prop} = new Map();}` - ); - unmarshalDictionaryProperties.push( - `instance.${prop}.set(key, ${unmarshalCode});` - ); - } - const corePropertyKeys = originalPropertyNames - .map((propertyKey) => `"${propertyKey}"`) - .join(','); - const unwrappedDictionaryCode = - setDictionaryProperties.length > 0 - ? `${setDictionaryProperties.join('\n')} - for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![${corePropertyKeys}].includes(key);}))) { - ${unmarshalDictionaryProperties.join('\n')} - }` - : ''; - - return ` -${unmarshalNormalProperties.join('\n')} - -${unwrappedDictionaryCode} -`; -} - -/** - * Render `unmarshal` function based on model - */ -function renderUnmarshal({ - renderer, - model -}: { - renderer: ClassRenderer; - model: ConstrainedObjectModel; -}): string { - const unmarshalProperties = renderUnmarshalProperties(model); - return `public static unmarshal(json: string | object): ${model.type} { - const obj = typeof json === "object" ? json : JSON.parse(json); - const instance = new ${model.type}({} as any); - -${renderer.indent(unmarshalProperties)} - return instance; -}`; -} - /** * Preset which adds `marshal`, `unmarshal`, `example` functions to class. * diff --git a/src/generators/typescript/presets/utils/MarshalFunction.ts b/src/generators/typescript/presets/utils/MarshalFunction.ts new file mode 100644 index 0000000000..a2b080a154 --- /dev/null +++ b/src/generators/typescript/presets/utils/MarshalFunction.ts @@ -0,0 +1,262 @@ +import { ClassRenderer } from '../../renderers/ClassRenderer'; +import { + getDictionary, + getNormalProperties, + getOriginalPropertyList +} from '../../../../helpers'; +import { + ConstrainedArrayModel, + ConstrainedDictionaryModel, + ConstrainedEnumModel, + ConstrainedMetaModel, + ConstrainedObjectModel, + ConstrainedObjectPropertyModel, + ConstrainedReferenceModel, + ConstrainedTupleModel, + ConstrainedUnionModel +} from '../../../../models'; + +function realizePropertyFactory(prop: string) { + return `$\{typeof ${prop} === 'number' || typeof ${prop} === 'boolean' ? ${prop} : JSON.stringify(${prop})}`; +} + +function renderMarshalProperty( + modelInstanceVariable: string, + model: ConstrainedMetaModel +) { + if ( + model instanceof ConstrainedReferenceModel && + !(model.ref instanceof ConstrainedEnumModel) + ) { + return `$\{${modelInstanceVariable}.marshal()}`; + } + + return realizePropertyFactory(modelInstanceVariable); +} +/** + * Render marshalling logic for tuples + */ +function renderTupleSerialization( + modelInstanceVariable: string, + unconstrainedProperty: string, + tuple: ConstrainedTupleModel +) { + const t = tuple.tuple.map((tupleEntry) => { + const temp = renderMarshalProperty( + `${modelInstanceVariable}[${tupleEntry.index}]`, + tupleEntry.value + ); + return `if(${modelInstanceVariable}[${tupleEntry.index}]) { + serializedTuple[${tupleEntry.index}] = ${temp} +} else { + serializedTuple[${tupleEntry.index}] = null; +}`; + }); + return `const serializedTuple = []; +${t.join('\n')} +json += \`"${unconstrainedProperty}": [\${serializedTuple.join(',')}],\`;`; +} + +/** + * Render marshalling logic for unions + */ +function renderUnionSerializationArray( + modelInstanceVariable: string, + prop: string, + unconstrainedProperty: string, + unionModel: ConstrainedUnionModel +) { + const propName = `${prop}JsonValues`; + const allUnionReferences = unionModel.union + .filter((model) => { + return ( + model instanceof ConstrainedReferenceModel && + !(model.ref instanceof ConstrainedEnumModel) + ); + }) + .map((model) => { + return `unionItem instanceof ${model.type}`; + }); + const allUnionReferencesCondition = allUnionReferences.join(' || '); + const hasUnionReference = allUnionReferences.length > 0; + let unionSerialization = `${propName}.push(typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem))`; + if (hasUnionReference) { + unionSerialization = `if(${allUnionReferencesCondition}) { + ${propName}.push(unionItem.marshal()); + } else { + ${propName}.push(typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem)) + }`; + } + return `const ${propName}: any[] = []; + for (const unionItem of ${modelInstanceVariable}) { + ${unionSerialization} + } + json += \`"${unconstrainedProperty}": [\${${propName}.join(',')}],\`;`; +} + +/** + * Render marshalling logic for Arrays + */ +function renderArraySerialization( + modelInstanceVariable: string, + prop: string, + unconstrainedProperty: string, + arrayModel: ConstrainedArrayModel +) { + const propName = `${prop}JsonValues`; + return `let ${propName}: any[] = []; + for (const unionItem of ${modelInstanceVariable}) { + ${propName}.push(\`${renderMarshalProperty( + 'unionItem', + arrayModel.valueModel + )}\`); + } + json += \`"${unconstrainedProperty}": [\${${propName}.join(',')}],\`;`; +} + +/** + * Render marshalling logic for unions + */ +function renderUnionSerialization( + modelInstanceVariable: string, + unconstrainedProperty: string, + unionModel: ConstrainedUnionModel +) { + const allUnionReferences = unionModel.union + .filter((model) => { + return ( + model instanceof ConstrainedReferenceModel && + !(model.ref instanceof ConstrainedEnumModel) + ); + }) + .map((model) => { + return `${modelInstanceVariable} instanceof ${model.type}`; + }); + const allUnionReferencesCondition = allUnionReferences.join(' || '); + const hasUnionReference = allUnionReferences.length > 0; + if (hasUnionReference) { + return `if(${allUnionReferencesCondition}) { + json += \`"${unconstrainedProperty}": $\{${modelInstanceVariable}.marshal()},\`; + } else { + json += \`"${unconstrainedProperty}": ${realizePropertyFactory( + modelInstanceVariable + )},\`; + }`; + } + return `json += \`"${unconstrainedProperty}": ${realizePropertyFactory( + modelInstanceVariable + )},\`;`; +} + +/** + * Render marshalling logic for dictionary types + */ +function renderDictionarySerialization( + properties: Record +) { + const unwrapDictionaryProperties = getDictionary(properties); + const originalPropertyNames = getOriginalPropertyList(properties); + return unwrapDictionaryProperties.map(([prop, propModel]) => { + let dictionaryValueType; + if ( + (propModel.property as ConstrainedDictionaryModel).value instanceof + ConstrainedUnionModel + ) { + dictionaryValueType = renderUnionSerialization( + 'value', + '${key}', + (propModel.property as ConstrainedDictionaryModel) + .value as ConstrainedUnionModel + ); + } else { + const type = renderMarshalProperty('value', propModel.property); + dictionaryValueType = `json += \`"$\{key}": ${type},\`;`; + } + return `if(this.${prop} !== undefined) { + for (const [key, value] of this.${prop}.entries()) { + //Only unwrap those that are not already a property in the JSON object + if([${originalPropertyNames + .map((value) => `"${value}"`) + .join(',')}].includes(String(key))) continue; + ${dictionaryValueType} + } +}`; + }); +} + +/** + * Render marshalling code for all the normal properties (not dictionaries with unwrap) + */ +function renderNormalProperties( + properties: Record +) { + const normalProperties = getNormalProperties(properties); + + return normalProperties.map(([prop, propModel]) => { + const modelInstanceVariable = `this.${prop}`; + let marshalCode; + if ( + propModel.property instanceof ConstrainedArrayModel && + propModel.property.valueModel instanceof ConstrainedUnionModel + ) { + marshalCode = renderUnionSerializationArray( + modelInstanceVariable, + prop, + propModel.unconstrainedPropertyName, + propModel.property.valueModel + ); + } else if (propModel.property instanceof ConstrainedUnionModel) { + marshalCode = renderUnionSerialization( + modelInstanceVariable, + propModel.unconstrainedPropertyName, + propModel.property + ); + } else if (propModel.property instanceof ConstrainedArrayModel) { + marshalCode = renderArraySerialization( + modelInstanceVariable, + prop, + propModel.unconstrainedPropertyName, + propModel.property + ); + } else if (propModel.property instanceof ConstrainedTupleModel) { + marshalCode = renderTupleSerialization( + modelInstanceVariable, + propModel.unconstrainedPropertyName, + propModel.property + ); + } else { + const propMarshalCode = renderMarshalProperty( + modelInstanceVariable, + propModel.property + ); + marshalCode = `json += \`"${propModel.unconstrainedPropertyName}": ${propMarshalCode},\`;`; + } + return `if(${modelInstanceVariable} !== undefined) { + ${marshalCode} +}`; + }); +} + +/** + * Render `marshal` function based on model + */ +export function renderMarshal({ + renderer, + model +}: { + renderer: ClassRenderer; + model: ConstrainedObjectModel; +}): string { + const properties = model.properties || {}; + const marshalNormalProperties = renderNormalProperties(properties); + const marshalUnwrapDictionaryProperties = + renderDictionarySerialization(properties); + + return `public marshal() : string { + let json = '{' +${renderer.indent(marshalNormalProperties.join('\n'))} +${renderer.indent(marshalUnwrapDictionaryProperties.join('\n'))} + //Remove potential last comma + return \`$\{json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; +}`; +} diff --git a/src/generators/typescript/presets/utils/UnmarshalFunction.ts b/src/generators/typescript/presets/utils/UnmarshalFunction.ts new file mode 100644 index 0000000000..373db05dba --- /dev/null +++ b/src/generators/typescript/presets/utils/UnmarshalFunction.ts @@ -0,0 +1,106 @@ +import { ClassRenderer } from '../../renderers/ClassRenderer'; +import { getDictionary, getNormalProperties } from '../../../../helpers'; +import { + ConstrainedDictionaryModel, + ConstrainedEnumModel, + ConstrainedMetaModel, + ConstrainedObjectModel, + ConstrainedObjectPropertyModel, + ConstrainedReferenceModel +} from '../../../../models'; + +/** + * Render the unmarshalled value + */ +function renderUnmarshalProperty( + modelInstanceVariable: string, + model: ConstrainedMetaModel +) { + if ( + model instanceof ConstrainedReferenceModel && + !(model.ref instanceof ConstrainedEnumModel) + ) { + return `${model.type}.unmarshal(${modelInstanceVariable})`; + } + return `${modelInstanceVariable}`; +} + +/** + * Render the code for unmarshalling of regular properties + */ +function unmarshalRegularProperty(propModel: ConstrainedObjectPropertyModel) { + const modelInstanceVariable = `obj["${propModel.unconstrainedPropertyName}"]`; + const unmarshalCode = renderUnmarshalProperty( + modelInstanceVariable, + propModel.property + ); + return `if (${modelInstanceVariable} !== undefined) { + instance.${propModel.propertyName} = ${unmarshalCode}; +}`; +} + +/** + * Render the code for unmarshalling unwrappable dictionary models + */ +function unmarshalDictionary(model: ConstrainedObjectModel) { + const setDictionaryProperties = []; + const unmarshalDictionaryProperties = []; + const properties = model.properties || {}; + const propertyKeys = [...Object.entries(properties)]; + const originalPropertyNames = propertyKeys.map(([, model]) => { + return model.unconstrainedPropertyName; + }); + const unwrapDictionaryProperties = getDictionary(properties); + + for (const [prop, propModel] of unwrapDictionaryProperties) { + const modelInstanceVariable = 'value as any'; + const unmarshalCode = renderUnmarshalProperty( + modelInstanceVariable, + (propModel.property as ConstrainedDictionaryModel).value + ); + setDictionaryProperties.push(`instance.${prop} = new Map();`); + unmarshalDictionaryProperties.push( + `instance.${prop}.set(key, ${unmarshalCode});` + ); + } + + const corePropertyKeys = originalPropertyNames + .map((propertyKey) => `"${propertyKey}"`) + .join(','); + if (setDictionaryProperties.length > 0) { + return `${setDictionaryProperties.join('\n')} +const propsToCheck = Object.entries(obj).filter((([key,]) => {return ![${corePropertyKeys}].includes(key);})); +for (const [key, value] of propsToCheck) { + ${unmarshalDictionaryProperties.join('\n')} +}`; + } + return ''; +} + +/** + * Render `unmarshal` function based on model + */ +export function renderUnmarshal({ + renderer, + model +}: { + renderer: ClassRenderer; + model: ConstrainedObjectModel; +}): string { + const properties = model.properties || {}; + const normalProperties = getNormalProperties(properties); + const unmarshalNormalProperties = normalProperties.map(([, propModel]) => + unmarshalRegularProperty(propModel) + ); + const unwrappedDictionaryCode = unmarshalDictionary(model); + + return `public static unmarshal(json: string | object): ${model.type} { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ${model.type}({} as any); + +${renderer.indent(unmarshalNormalProperties.join('\n'))} + +${renderer.indent(unwrappedDictionaryCode)} + return instance; +}`; +} diff --git a/src/helpers/FilterHelpers.ts b/src/helpers/FilterHelpers.ts new file mode 100644 index 0000000000..7235228dc5 --- /dev/null +++ b/src/helpers/FilterHelpers.ts @@ -0,0 +1,42 @@ +import { + ConstrainedDictionaryModel, + ConstrainedObjectPropertyModel +} from '../models'; + +/** + * Filter out all properties that are dictionary models with unwrap serialization type. + */ +export function getNormalProperties( + properties: Record +): [string, ConstrainedObjectPropertyModel][] { + return Object.entries(properties).filter( + ([, value]) => + !(value.property instanceof ConstrainedDictionaryModel) || + (value.property instanceof ConstrainedDictionaryModel && + value.property.serializationType !== 'unwrap') + ); +} + +/** + * Filter out all properties that are dictionary models with unwrap serialization type. + */ +export function getDictionary( + properties: Record +): [string, ConstrainedObjectPropertyModel][] { + return Object.entries(properties).filter( + ([, value]) => + value.property instanceof ConstrainedDictionaryModel && + value.property.serializationType === 'unwrap' + ); +} + +/** + * Filter properties and return unconstrained property names for each property + */ +export function getOriginalPropertyList( + properties: Record +): string[] { + return Object.entries(properties).map(([, model]) => { + return model.unconstrainedPropertyName; + }); +} diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 4624b77036..c1c75769dc 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -7,3 +7,4 @@ export * from './Constraints'; export * from './ConstrainHelpers'; export * from './PresetHelpers'; export * from './DependencyHelpers'; +export * from './FilterHelpers'; diff --git a/test/generators/typescript/preset/MarshallingPreset.spec.ts b/test/generators/typescript/preset/MarshallingPreset.spec.ts index 80e6e32f4e..920c9cd9bc 100644 --- a/test/generators/typescript/preset/MarshallingPreset.spec.ts +++ b/test/generators/typescript/preset/MarshallingPreset.spec.ts @@ -14,7 +14,11 @@ const doc = { }, $id: 'Test', type: 'object', - additionalProperties: { $ref: '#/definitions/NestedTest' }, + additionalProperties: { + oneOf: [ + {$ref: '#/definitions/NestedTest'}, + {type: 'string'} + ] }, required: ['string prop'], properties: { 'string prop': { type: 'string' }, @@ -28,6 +32,9 @@ const doc = { oneOf: [ { $ref: '#/definitions/NestedTest' + }, + { + type: 'string' } ] }, @@ -51,6 +58,18 @@ const doc = { items: { $ref: '#/definitions/NestedTest' } + }, + tupleTest: { + type: 'array', + additionalItems: false, + items: [ + { + $ref: '#/definitions/NestedTest' + }, + { + type: 'string' + } + ] } } }; diff --git a/test/generators/typescript/preset/__snapshots__/JsonBinPackPreset.spec.ts.snap b/test/generators/typescript/preset/__snapshots__/JsonBinPackPreset.spec.ts.snap index 8e23a3f4c3..09799ca277 100644 --- a/test/generators/typescript/preset/__snapshots__/JsonBinPackPreset.spec.ts.snap +++ b/test/generators/typescript/preset/__snapshots__/JsonBinPackPreset.spec.ts.snap @@ -22,16 +22,15 @@ exports[`JsonBinPack preset should work fine with AsyncAPI inputs 1`] = ` public marshal() : string { let json = '{' if(this.email !== undefined) { - json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; + json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; } if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { - //Only unwrap those who are not already a property in the JSON object - if(Object.keys(this).includes(String(key))) continue; + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if([\\"email\\",\\"additionalProperties\\"].includes(String(key))) continue; json += \`\\"\${key}\\": \${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},\`; } } - //Remove potential last comma return \`\${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; } @@ -43,12 +42,12 @@ exports[`JsonBinPack preset should work fine with AsyncAPI inputs 1`] = ` if (obj[\\"email\\"] !== undefined) { instance.email = obj[\\"email\\"]; } - - if (instance.additionalProperties === undefined) {instance.additionalProperties = new Map();} - for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![\\"email\\",\\"additionalProperties\\"].includes(key);}))) { - instance.additionalProperties.set(key, value as any); - } - + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return ![\\"email\\",\\"additionalProperties\\"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } return instance; } @@ -88,16 +87,15 @@ exports[`JsonBinPack preset should work fine with JSON Schema draft 4 1`] = ` public marshal() : string { let json = '{' if(this.email !== undefined) { - json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; + json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; } if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { - //Only unwrap those who are not already a property in the JSON object - if(Object.keys(this).includes(String(key))) continue; + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if([\\"email\\",\\"additionalProperties\\"].includes(String(key))) continue; json += \`\\"\${key}\\": \${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},\`; } } - //Remove potential last comma return \`\${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; } @@ -109,12 +107,12 @@ exports[`JsonBinPack preset should work fine with JSON Schema draft 4 1`] = ` if (obj[\\"email\\"] !== undefined) { instance.email = obj[\\"email\\"]; } - - if (instance.additionalProperties === undefined) {instance.additionalProperties = new Map();} - for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![\\"email\\",\\"additionalProperties\\"].includes(key);}))) { - instance.additionalProperties.set(key, value as any); - } - + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return ![\\"email\\",\\"additionalProperties\\"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } return instance; } @@ -154,16 +152,15 @@ exports[`JsonBinPack preset should work fine with JSON Schema draft 6 1`] = ` public marshal() : string { let json = '{' if(this.email !== undefined) { - json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; + json += \`\\"email\\": \${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},\`; } if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { - //Only unwrap those who are not already a property in the JSON object - if(Object.keys(this).includes(String(key))) continue; + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if([\\"email\\",\\"additionalProperties\\"].includes(String(key))) continue; json += \`\\"\${key}\\": \${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},\`; } } - //Remove potential last comma return \`\${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; } @@ -175,12 +172,12 @@ exports[`JsonBinPack preset should work fine with JSON Schema draft 6 1`] = ` if (obj[\\"email\\"] !== undefined) { instance.email = obj[\\"email\\"]; } - - if (instance.additionalProperties === undefined) {instance.additionalProperties = new Map();} - for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![\\"email\\",\\"additionalProperties\\"].includes(key);}))) { - instance.additionalProperties.set(key, value as any); - } - + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return ![\\"email\\",\\"additionalProperties\\"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } return instance; } diff --git a/test/generators/typescript/preset/__snapshots__/MarshallingPreset.spec.ts.snap b/test/generators/typescript/preset/__snapshots__/MarshallingPreset.spec.ts.snap index 8342b7fb67..763b32ab54 100644 --- a/test/generators/typescript/preset/__snapshots__/MarshallingPreset.spec.ts.snap +++ b/test/generators/typescript/preset/__snapshots__/MarshallingPreset.spec.ts.snap @@ -6,20 +6,22 @@ exports[`Marshalling preset should render un/marshal code 1`] = ` private _enumProp?: EnumTest; private _numberProp?: number; private _nestedObject?: NestedTest; - private _unionTest?: NestedTest; + private _unionTest?: NestedTest | string; private _unionArrayTest?: (NestedTest | string)[]; private _arrayTest?: NestedTest[]; - private _additionalProperties?: Map; + private _tupleTest?: [NestedTest, string]; + private _additionalProperties?: Map; constructor(input: { stringProp: string, enumProp?: EnumTest, numberProp?: number, nestedObject?: NestedTest, - unionTest?: NestedTest, + unionTest?: NestedTest | string, unionArrayTest?: (NestedTest | string)[], arrayTest?: NestedTest[], - additionalProperties?: Map, + tupleTest?: [NestedTest, string], + additionalProperties?: Map, }) { this._stringProp = input.stringProp; this._enumProp = input.enumProp; @@ -28,6 +30,7 @@ exports[`Marshalling preset should render un/marshal code 1`] = ` this._unionTest = input.unionTest; this._unionArrayTest = input.unionArrayTest; this._arrayTest = input.arrayTest; + this._tupleTest = input.tupleTest; this._additionalProperties = input.additionalProperties; } @@ -43,8 +46,8 @@ exports[`Marshalling preset should render un/marshal code 1`] = ` get nestedObject(): NestedTest | undefined { return this._nestedObject; } set nestedObject(nestedObject: NestedTest | undefined) { this._nestedObject = nestedObject; } - get unionTest(): NestedTest | undefined { return this._unionTest; } - set unionTest(unionTest: NestedTest | undefined) { this._unionTest = unionTest; } + get unionTest(): NestedTest | string | undefined { return this._unionTest; } + set unionTest(unionTest: NestedTest | string | undefined) { this._unionTest = unionTest; } get unionArrayTest(): (NestedTest | string)[] | undefined { return this._unionArrayTest; } set unionArrayTest(unionArrayTest: (NestedTest | string)[] | undefined) { this._unionArrayTest = unionArrayTest; } @@ -52,32 +55,35 @@ exports[`Marshalling preset should render un/marshal code 1`] = ` get arrayTest(): NestedTest[] | undefined { return this._arrayTest; } set arrayTest(arrayTest: NestedTest[] | undefined) { this._arrayTest = arrayTest; } - get additionalProperties(): Map | undefined { return this._additionalProperties; } - set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + get tupleTest(): [NestedTest, string] | undefined { return this._tupleTest; } + set tupleTest(tupleTest: [NestedTest, string] | undefined) { this._tupleTest = tupleTest; } + + get additionalProperties(): Map | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } public marshal() : string { let json = '{' if(this.stringProp !== undefined) { - json += \`\\"string prop\\": \${typeof this.stringProp === 'number' || typeof this.stringProp === 'boolean' ? this.stringProp : JSON.stringify(this.stringProp)},\`; + json += \`\\"string prop\\": \${typeof this.stringProp === 'number' || typeof this.stringProp === 'boolean' ? this.stringProp : JSON.stringify(this.stringProp)},\`; } if(this.enumProp !== undefined) { - json += \`\\"enumProp\\": \${typeof this.enumProp === 'number' || typeof this.enumProp === 'boolean' ? this.enumProp : JSON.stringify(this.enumProp)},\`; + json += \`\\"enumProp\\": \${typeof this.enumProp === 'number' || typeof this.enumProp === 'boolean' ? this.enumProp : JSON.stringify(this.enumProp)},\`; } if(this.numberProp !== undefined) { - json += \`\\"numberProp\\": \${typeof this.numberProp === 'number' || typeof this.numberProp === 'boolean' ? this.numberProp : JSON.stringify(this.numberProp)},\`; + json += \`\\"numberProp\\": \${typeof this.numberProp === 'number' || typeof this.numberProp === 'boolean' ? this.numberProp : JSON.stringify(this.numberProp)},\`; } if(this.nestedObject !== undefined) { - json += \`\\"nestedObject\\": \${this.nestedObject.marshal()},\`; + json += \`\\"nestedObject\\": \${this.nestedObject.marshal()},\`; } if(this.unionTest !== undefined) { if(this.unionTest instanceof NestedTest) { - json += \`\\"unionTest\\": \${this.unionTest.marshal()},\`; - } else { - json += \`\\"unionTest\\": \${typeof this.unionTest === 'number' || typeof this.unionTest === 'boolean' ? this.unionTest : JSON.stringify(this.unionTest)},\`; - } + json += \`\\"unionTest\\": \${this.unionTest.marshal()},\`; + } else { + json += \`\\"unionTest\\": \${typeof this.unionTest === 'number' || typeof this.unionTest === 'boolean' ? this.unionTest : JSON.stringify(this.unionTest)},\`; + } } if(this.unionArrayTest !== undefined) { - let unionArrayTestJsonValues: any[] = []; + const unionArrayTestJsonValues: any[] = []; for (const unionItem of this.unionArrayTest) { if(unionItem instanceof NestedTest) { unionArrayTestJsonValues.push(unionItem.marshal()); @@ -85,23 +91,40 @@ exports[`Marshalling preset should render un/marshal code 1`] = ` unionArrayTestJsonValues.push(typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem)) } } - json += \`\\"unionArrayTest\\": [\${unionArrayTestJsonValues.join(',')}],\`; + json += \`\\"unionArrayTest\\": [\${unionArrayTestJsonValues.join(',')}],\`; } if(this.arrayTest !== undefined) { let arrayTestJsonValues: any[] = []; for (const unionItem of this.arrayTest) { arrayTestJsonValues.push(\`\${unionItem.marshal()}\`); } - json += \`\\"arrayTest\\": [\${arrayTestJsonValues.join(',')}],\`; + json += \`\\"arrayTest\\": [\${arrayTestJsonValues.join(',')}],\`; + } + if(this.tupleTest !== undefined) { + const serializedTuple = []; + if(this.tupleTest[0]) { + serializedTuple[0] = \${this.tupleTest[0].marshal()} + } else { + serializedTuple[0] = null; + } + if(this.tupleTest[1]) { + serializedTuple[1] = \${typeof this.tupleTest[1] === 'number' || typeof this.tupleTest[1] === 'boolean' ? this.tupleTest[1] : JSON.stringify(this.tupleTest[1])} + } else { + serializedTuple[1] = null; + } + json += \`\\"tupleTest\\": [\${serializedTuple.join(',')}],\`; } if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { - //Only unwrap those who are not already a property in the JSON object - if(Object.keys(this).includes(String(key))) continue; - json += \`\\"\${key}\\": \${value.marshal()},\`; + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if([\\"string prop\\",\\"enumProp\\",\\"numberProp\\",\\"nestedObject\\",\\"unionTest\\",\\"unionArrayTest\\",\\"arrayTest\\",\\"tupleTest\\",\\"additionalProperties\\"].includes(String(key))) continue; + if(value instanceof NestedTest) { + json += \`\\"\${key}\\": \${value.marshal()},\`; + } else { + json += \`\\"\${key}\\": \${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},\`; + } } } - //Remove potential last comma return \`\${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; } @@ -131,12 +154,15 @@ exports[`Marshalling preset should render un/marshal code 1`] = ` if (obj[\\"arrayTest\\"] !== undefined) { instance.arrayTest = obj[\\"arrayTest\\"]; } - - if (instance.additionalProperties === undefined) {instance.additionalProperties = new Map();} - for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![\\"string prop\\",\\"enumProp\\",\\"numberProp\\",\\"nestedObject\\",\\"unionTest\\",\\"unionArrayTest\\",\\"arrayTest\\",\\"additionalProperties\\"].includes(key);}))) { - instance.additionalProperties.set(key, NestedTest.unmarshal(value as any)); - } - + if (obj[\\"tupleTest\\"] !== undefined) { + instance.tupleTest = obj[\\"tupleTest\\"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return ![\\"string prop\\",\\"enumProp\\",\\"numberProp\\",\\"nestedObject\\",\\"unionTest\\",\\"unionArrayTest\\",\\"arrayTest\\",\\"tupleTest\\",\\"additionalProperties\\"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } return instance; } }" @@ -173,16 +199,15 @@ exports[`Marshalling preset should render un/marshal code 3`] = ` public marshal() : string { let json = '{' if(this.stringProp !== undefined) { - json += \`\\"stringProp\\": \${typeof this.stringProp === 'number' || typeof this.stringProp === 'boolean' ? this.stringProp : JSON.stringify(this.stringProp)},\`; + json += \`\\"stringProp\\": \${typeof this.stringProp === 'number' || typeof this.stringProp === 'boolean' ? this.stringProp : JSON.stringify(this.stringProp)},\`; } if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { - //Only unwrap those who are not already a property in the JSON object - if(Object.keys(this).includes(String(key))) continue; + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if([\\"stringProp\\",\\"additionalProperties\\"].includes(String(key))) continue; json += \`\\"\${key}\\": \${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},\`; } } - //Remove potential last comma return \`\${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; } @@ -194,12 +219,12 @@ exports[`Marshalling preset should render un/marshal code 3`] = ` if (obj[\\"stringProp\\"] !== undefined) { instance.stringProp = obj[\\"stringProp\\"]; } - - if (instance.additionalProperties === undefined) {instance.additionalProperties = new Map();} - for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![\\"stringProp\\",\\"additionalProperties\\"].includes(key);}))) { - instance.additionalProperties.set(key, value as any); - } - + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return ![\\"stringProp\\",\\"additionalProperties\\"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } return instance; } }"