├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── pic.png ├── src ├── application │ └── Modelify.ts ├── domain │ ├── codeGeneration │ │ ├── CodeGenerator.ts │ │ ├── CodeLine.ts │ │ ├── SourceCode.ts │ │ └── SourceFile.ts │ ├── logger │ │ └── BaseLogger.ts │ ├── schema │ │ ├── schema │ │ │ ├── ApiMethodParam.ts │ │ │ ├── ApiMethodScheme.ts │ │ │ ├── ClassField.ts │ │ │ └── ClassScheme.ts │ │ └── types │ │ │ ├── AnyType.ts │ │ │ ├── BooleanType.ts │ │ │ ├── CustomType.ts │ │ │ ├── IntBoolType.ts │ │ │ ├── NumberType.ts │ │ │ ├── ObjectType.ts │ │ │ ├── StringType.ts │ │ │ ├── Type.ts │ │ │ └── VectorType.ts │ └── schemeGeneration │ │ ├── SchemeGenerator.ts │ │ └── SchemeSource.ts ├── infrastructure │ ├── codeGeneration │ │ ├── JavaScriptCodeGenerator.ts │ │ ├── SwiftCodeGenerator.ts │ │ ├── TypescriptCodeGenerator.ts │ │ └── Utils.ts │ ├── logger │ │ └── ConsoleLogger.ts │ └── schemeGeneration │ │ ├── JSONSchemeGenerator.ts │ │ └── sources │ │ └── JSONSchemeSource.ts ├── presentation │ ├── BaseConsoleApplication.ts │ └── ModelifyApplication.ts └── test │ ├── JSONSchemeGeneratorTest.ts │ └── Run.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | build -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Narek Abovyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![pic](pic.png) 2 | 3 | # Modelify 4 | 5 | Modelify is a tool that creates models from JSON. 6 | 7 | ## Installation 8 | 9 | To install the stable version: 10 | 11 | ```bash 12 | npm install modelify -g 13 | ``` 14 | 15 | This will install Modelify globally so that it may be run from the command line. 16 | 17 | ## Usage 18 | 19 | ```bash 20 | modelify -lang=lang_name -i=/path/to/your.json -o=/path/to/out/dir 21 | ``` 22 | 23 | `lang_name` Output models language (js or ts or swift) 24 | 25 | `/path/to/your.json` Path to some json 26 | 27 | `/path/to/out/dir` Output path 28 | 29 | ## Currently available languages 30 | 31 | * JavaScript 32 | * TypeScript 33 | * Swift -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modelify", 3 | "version": "1.0.9", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node ./build/test/Run.js", 8 | "build": "./node_modules/.bin/tsc --p ./tsconfig.json", 9 | "prepublish": "./node_modules/.bin/tsc --p ./tsconfig.json" 10 | }, 11 | "author": "Narek Abovyan ", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@types/node": "^7.0.18", 15 | "typescript": "^2.3.2", 16 | "typings": "^2.1.0" 17 | }, 18 | "bin": { 19 | "modelify": "./build/presentation/ModelifyApplication.js" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naltox/modelify/cf63f7467fcca03eb29dd7b1d614ddc3a640bcc2/pic.png -------------------------------------------------------------------------------- /src/application/Modelify.ts: -------------------------------------------------------------------------------- 1 | import {SchemeGenerator} from "../domain/schemeGeneration/SchemeGenerator"; 2 | import {CodeGenerator} from "../domain/codeGeneration/CodeGenerator"; 3 | import SourceFile from "../domain/codeGeneration/SourceFile"; 4 | import {SchemeSource} from "../domain/schemeGeneration/SchemeSource"; 5 | import BaseLogger from "../domain/logger/BaseLogger"; 6 | 7 | export default class Modelify { 8 | constructor( 9 | private schemeGenerator: SchemeGenerator, 10 | private codeGenerator: CodeGenerator, 11 | private logger: BaseLogger 12 | ) { 13 | 14 | } 15 | 16 | public generate(schemeSource: SchemeSource): SourceFile[] { 17 | let scheme = this.schemeGenerator.generate(schemeSource) 18 | 19 | return scheme.map(s => { 20 | let file = this.codeGenerator.generateClass(s) 21 | 22 | this.logger.log(`Generated model: ${file.fileName}`) 23 | 24 | return file 25 | }) 26 | } 27 | } -------------------------------------------------------------------------------- /src/domain/codeGeneration/CodeGenerator.ts: -------------------------------------------------------------------------------- 1 | import ClassScheme from "../schema/schema/ClassScheme"; 2 | import SourceCode from "./SourceCode"; 3 | import ApiMethodScheme from "../schema/schema/ApiMethodScheme"; 4 | import SourceFile from "./SourceFile"; 5 | 6 | export interface CodeGenerator { 7 | generateClass(scheme: ClassScheme): SourceFile 8 | 9 | //generateApiMethod(scheme: ApiMethodScheme): SourceCode 10 | 11 | //generateApiMethodParamsInterface(scheme: ApiMethodScheme): SourceCode 12 | } -------------------------------------------------------------------------------- /src/domain/codeGeneration/CodeLine.ts: -------------------------------------------------------------------------------- 1 | export default class CodeLine { 2 | private _data: string 3 | 4 | constructor(data: string, tab: number = 0) { 5 | this._data = this.genTab(tab) + data 6 | } 7 | 8 | get data(): string { 9 | return this._data 10 | } 11 | 12 | public tab(n: number) { 13 | this._data = this.genTab(n) + this._data 14 | } 15 | 16 | private genTab(n: number): string { 17 | return new Array(n).fill(' ').join('') 18 | } 19 | } -------------------------------------------------------------------------------- /src/domain/codeGeneration/SourceCode.ts: -------------------------------------------------------------------------------- 1 | import CodeLine from "./CodeLine"; 2 | 3 | export default class SourceCode { 4 | private _lines: CodeLine[] 5 | 6 | constructor(lines: CodeLine[] = []) { 7 | this._lines = lines 8 | } 9 | 10 | public add(data: string, tab: number = 0): SourceCode { 11 | this._lines.push(new CodeLine(data, tab)) 12 | return this 13 | } 14 | 15 | public append(code: SourceCode, tab: number = 0) { 16 | code.tab(tab) 17 | this._lines.push(...code.lines) 18 | } 19 | 20 | public render(): string { 21 | return this._lines.map(line => line.data).join('\n') 22 | } 23 | 24 | public tab(n: number) { 25 | this._lines.forEach(line => line.tab(n)) 26 | } 27 | 28 | get lines(): CodeLine[] { 29 | return this._lines 30 | } 31 | 32 | private genTab(n: number): string { 33 | return new Array(n).join(' ') 34 | } 35 | } -------------------------------------------------------------------------------- /src/domain/codeGeneration/SourceFile.ts: -------------------------------------------------------------------------------- 1 | import SourceCode from "./SourceCode"; 2 | 3 | export default class SourceFile { 4 | constructor( 5 | readonly fileName: string, 6 | readonly code: SourceCode 7 | ) { 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /src/domain/logger/BaseLogger.ts: -------------------------------------------------------------------------------- 1 | interface BaseLogger { 2 | log(prefix: string, data?: any, recursiveDepth?: boolean): void 3 | warn(prefix: string, data?: any, recursiveDepth?: boolean): void 4 | error(prefix: string, data?: any, recursiveDepth?: boolean): void 5 | } 6 | 7 | export default BaseLogger -------------------------------------------------------------------------------- /src/domain/schema/schema/ApiMethodParam.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "../types/Type"; 2 | 3 | export default class ApiMethodParam { 4 | constructor( 5 | readonly name: string, 6 | readonly type: Type, 7 | readonly required: boolean, 8 | readonly description: string 9 | ) { 10 | 11 | } 12 | 13 | public serialize(): Object { 14 | return { 15 | name: this.name, 16 | type: this.type.serialize(), 17 | required: this.required, 18 | description: this.description 19 | } 20 | } 21 | 22 | public static deserialize(raw: any): ApiMethodParam { 23 | return new ApiMethodParam( 24 | raw['name'], 25 | Type.deserialize(raw['type']), 26 | raw['required'], 27 | raw['description'] 28 | ) 29 | } 30 | } -------------------------------------------------------------------------------- /src/domain/schema/schema/ApiMethodScheme.ts: -------------------------------------------------------------------------------- 1 | import ApiMethodParam from "./ApiMethodParam"; 2 | import {Type} from "../types/Type"; 3 | 4 | export default class ApiMethodScheme { 5 | constructor( 6 | readonly name: string, 7 | readonly params: ApiMethodParam[], 8 | readonly responseType: Type, 9 | readonly description: string 10 | ) { 11 | 12 | } 13 | 14 | public serialize(): Object { 15 | return { 16 | name: this.name, 17 | params: this.params.map(p => p.serialize()), 18 | responseType: this.responseType.serialize(), 19 | description: this.description 20 | } 21 | } 22 | 23 | public static deserialize(raw: any): ApiMethodScheme { 24 | return new ApiMethodScheme( 25 | raw['name'], 26 | raw['params'].map(ApiMethodParam.deserialize), 27 | Type.deserialize(raw['responseType']), 28 | raw['description'] 29 | ) 30 | } 31 | } -------------------------------------------------------------------------------- /src/domain/schema/schema/ClassField.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "../types/Type"; 2 | 3 | export default class ClassField { 4 | constructor( 5 | readonly name: string, 6 | readonly type: Type, 7 | readonly description: string 8 | ) { 9 | 10 | } 11 | 12 | public serialize(): Object { 13 | return { 14 | name: this.name, 15 | type: this.type.serialize(), 16 | description: this.description 17 | } 18 | } 19 | 20 | public static deserialize(raw: any): ClassField { 21 | return new ClassField( 22 | raw['name'], 23 | Type.deserialize(raw['type']), 24 | raw['description'] 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /src/domain/schema/schema/ClassScheme.ts: -------------------------------------------------------------------------------- 1 | import ClassField from "./ClassField"; 2 | 3 | export default class ClassScheme { 4 | constructor( 5 | readonly name: string, 6 | readonly fields: ClassField[] 7 | ) { 8 | 9 | } 10 | 11 | public serialize(): Object { 12 | return { 13 | name: this.name, 14 | fields: this.fields.map(f => f.serialize()) 15 | } 16 | } 17 | 18 | public static deserialize(raw: any): ClassScheme { 19 | return new ClassScheme( 20 | raw['name'], 21 | raw['fields'].map(ClassField.deserialize) 22 | ) 23 | } 24 | } -------------------------------------------------------------------------------- /src/domain/schema/types/AnyType.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "./Type"; 2 | 3 | export default class AnyType implements Type { 4 | public serialize(): Object { 5 | return { 6 | type: 'AnyType' 7 | } 8 | } 9 | 10 | public static deserialize(raw: Object): AnyType { 11 | return new AnyType() 12 | } 13 | } -------------------------------------------------------------------------------- /src/domain/schema/types/BooleanType.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "./Type"; 2 | 3 | export default class BooleanType implements Type { 4 | public serialize(): Object { 5 | return { 6 | type: 'BooleanType' 7 | } 8 | } 9 | 10 | public static deserialize(raw: Object): BooleanType { 11 | return new BooleanType() 12 | } 13 | } -------------------------------------------------------------------------------- /src/domain/schema/types/CustomType.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "./Type"; 2 | 3 | export default class CustomType implements Type { 4 | constructor(readonly name: string) { 5 | 6 | } 7 | 8 | public serialize(): Object { 9 | return { 10 | type: 'CustomType', 11 | name: this.name 12 | } 13 | } 14 | 15 | public static deserialize(raw: any): CustomType { 16 | return new CustomType(raw['name']) 17 | } 18 | } -------------------------------------------------------------------------------- /src/domain/schema/types/IntBoolType.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "./Type"; 2 | 3 | export default class IntBoolType implements Type { 4 | serialize(): Object { 5 | return {} 6 | } 7 | } -------------------------------------------------------------------------------- /src/domain/schema/types/NumberType.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "./Type"; 2 | 3 | export default class NumberType implements Type { 4 | public serialize(): Object { 5 | return { 6 | type: 'NumberType' 7 | } 8 | } 9 | 10 | public static deserialize(raw: Object): NumberType { 11 | return new NumberType() 12 | } 13 | } -------------------------------------------------------------------------------- /src/domain/schema/types/ObjectType.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "./Type"; 2 | 3 | export default class ObjectType implements Type { 4 | public serialize(): Object { 5 | return { 6 | type: 'ObjectType' 7 | } 8 | } 9 | 10 | public static deserialize(raw: Object): ObjectType { 11 | return new ObjectType() 12 | } 13 | } -------------------------------------------------------------------------------- /src/domain/schema/types/StringType.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "./Type"; 2 | 3 | export default class StringType implements Type { 4 | public serialize(): Object { 5 | return { 6 | type: 'StringType' 7 | } 8 | } 9 | 10 | public static deserialize(raw: Object): StringType { 11 | return new StringType() 12 | } 13 | } -------------------------------------------------------------------------------- /src/domain/schema/types/Type.ts: -------------------------------------------------------------------------------- 1 | import AnyType from "./AnyType"; 2 | import BooleanType from "./BooleanType"; 3 | import CustomType from "./CustomType"; 4 | import NumberType from "./NumberType"; 5 | import StringType from "./StringType"; 6 | import VectorType from "./VectorType"; 7 | import ObjectType from "./ObjectType"; 8 | 9 | export abstract class Type { 10 | public abstract serialize(): Object 11 | 12 | public static deserialize(raw: any): Type { 13 | switch (raw['type']) { 14 | case 'AnyType': 15 | return AnyType.deserialize(raw) 16 | case 'BooleanType': 17 | return BooleanType.deserialize(raw) 18 | case 'CustomType': 19 | return CustomType.deserialize(raw) 20 | case 'NumberType': 21 | return NumberType.deserialize(raw) 22 | case 'StringType': 23 | return StringType.deserialize(raw) 24 | case 'VectorType': 25 | return VectorType.deserialize(raw) 26 | case 'ObjectType': 27 | return ObjectType.deserialize(raw) 28 | } 29 | 30 | throw new Error('Unknown type: ' + JSON.stringify(raw)) 31 | } 32 | } -------------------------------------------------------------------------------- /src/domain/schema/types/VectorType.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "./Type"; 2 | 3 | export default class VectorType implements Type { 4 | constructor(readonly item: Type) { 5 | 6 | } 7 | 8 | public serialize(): Object { 9 | return { 10 | type: 'VectorType', 11 | item: this.item.serialize() 12 | } 13 | } 14 | 15 | public static deserialize(raw: any): VectorType { 16 | return new VectorType(Type.deserialize(raw['item'])) 17 | } 18 | } -------------------------------------------------------------------------------- /src/domain/schemeGeneration/SchemeGenerator.ts: -------------------------------------------------------------------------------- 1 | import {SchemeSource} from "./SchemeSource"; 2 | import ClassScheme from "../schema/schema/ClassScheme"; 3 | 4 | export abstract class SchemeGenerator { 5 | public abstract generate(source: SchemeSource): ClassScheme[] 6 | } -------------------------------------------------------------------------------- /src/domain/schemeGeneration/SchemeSource.ts: -------------------------------------------------------------------------------- 1 | export interface SchemeSource { 2 | 3 | } -------------------------------------------------------------------------------- /src/infrastructure/codeGeneration/JavaScriptCodeGenerator.ts: -------------------------------------------------------------------------------- 1 | import {CodeGenerator} from "../../domain/codeGeneration/CodeGenerator"; 2 | import ClassScheme from "../../domain/schema/schema/ClassScheme"; 3 | import SourceFile from "../../domain/codeGeneration/SourceFile"; 4 | import CustomType from "../../domain/schema/types/CustomType"; 5 | import {Type} from "../../domain/schema/types/Type"; 6 | import SourceCode from "../../domain/codeGeneration/SourceCode"; 7 | import {toCamelCase} from "./Utils"; 8 | import VectorType from "../../domain/schema/types/VectorType"; 9 | import IntBoolType from "../../domain/schema/types/IntBoolType"; 10 | import StringType from "../../domain/schema/types/StringType"; 11 | import AnyType from "../../domain/schema/types/AnyType"; 12 | import NumberType from "../../domain/schema/types/NumberType"; 13 | import BooleanType from "../../domain/schema/types/BooleanType"; 14 | 15 | export default class JavaScriptCodeGenerator implements CodeGenerator { 16 | public generateClass(scheme: ClassScheme): SourceFile { 17 | let code = new SourceCode() 18 | let imports = this.generateImports(scheme) 19 | let constructor = this.generateClassConstructor(scheme) 20 | let deserializeMethod = this.generateDeserializeMethod(scheme) 21 | let serializeMethod = this.generateSerializeMethod(scheme) 22 | 23 | code.append(imports) 24 | code.add('') 25 | code.add(`class ${scheme.name} {`) 26 | code.append(constructor, 1) 27 | code.add('') 28 | code.append(deserializeMethod, 1) 29 | code.add('') 30 | code.append(serializeMethod, 1) 31 | code.add('}') 32 | code.add('') 33 | code.add(`module.exports = ${scheme.name}`) 34 | 35 | return new SourceFile(`${scheme.name}.js`, code) 36 | } 37 | 38 | private generateImports(scheme: ClassScheme): SourceCode { 39 | let code = new SourceCode() 40 | 41 | scheme.fields.forEach((field, index) => { 42 | let customType = this.getCustomType(field.type) 43 | 44 | if (customType) { 45 | code.add(`const ${customType.name} = require('./${customType.name}')`) 46 | } 47 | }) 48 | 49 | return code 50 | } 51 | 52 | private generateClassConstructor(scheme: ClassScheme): SourceCode { 53 | let code = new SourceCode() 54 | let jsdoc = this.generateClassConstructorJSDoc(scheme) 55 | 56 | code.append(jsdoc) 57 | 58 | code.add('constructor (') 59 | 60 | scheme.fields.forEach((field, index) => { 61 | let coma = this.genComa(scheme.fields, index) 62 | 63 | code.add(`${toCamelCase(field.name)}${coma}`, 1) 64 | }) 65 | 66 | code.add(') {') 67 | scheme.fields.forEach((field, index) => { 68 | code.add(`this.${toCamelCase(field.name)} = ${toCamelCase(field.name)}`, 1) 69 | }) 70 | code.add('}') 71 | 72 | return code 73 | } 74 | 75 | private generateClassConstructorJSDoc(scheme: ClassScheme): SourceCode { 76 | let code = new SourceCode() 77 | 78 | code.add('/**') 79 | code.add(' * @class') 80 | 81 | scheme.fields.forEach(field => { 82 | code.add(` * @property {${this.renderType(field.type)}} ${toCamelCase(field.name)} ${field.description}`) 83 | }) 84 | 85 | code.add(' */') 86 | 87 | return code 88 | } 89 | 90 | private generateDeserializeMethod(scheme: ClassScheme): SourceCode { 91 | let code = new SourceCode() 92 | 93 | code.add('/**') 94 | code.add(' * @param {Object} raw') 95 | code.add(` * @returns {${scheme.name}}`) 96 | code.add(' */') 97 | 98 | code.add(`static deserialize(raw) {`) 99 | code.add(`return new ${scheme.name} (`, 1) 100 | 101 | scheme.fields.forEach((field, index) => { 102 | let coma = this.genComa(scheme.fields, index) 103 | let fieldVar = `raw['${field.name}']` 104 | 105 | if (field.type instanceof VectorType) 106 | code.add(this.renderVectorDeserialize(fieldVar, field.type) + coma, 2) 107 | else if (field.type instanceof CustomType) 108 | code.add(`${fieldVar} ? ${field.type.name}.deserialize(${fieldVar}) : undefined${coma}`, 2) 109 | else if (field.type instanceof IntBoolType) 110 | code.add(`!!${fieldVar}${coma}`, 2) 111 | else 112 | code.add(fieldVar + coma, 2) 113 | }) 114 | 115 | code.add(`)`, 1) 116 | code.add('}') 117 | 118 | return code 119 | } 120 | 121 | private generateSerializeMethod(scheme: ClassScheme): SourceCode { 122 | let code = new SourceCode() 123 | 124 | code.add('/**') 125 | code.add(` * @returns {Object}`) 126 | code.add(' */') 127 | 128 | code.add(`serialize() {`) 129 | 130 | code.add(`return {`, 1) 131 | 132 | 133 | scheme.fields.forEach((field, index) => { 134 | let coma = this.genComa(scheme.fields, index) 135 | let fieldVar = `${field.name}: this.${toCamelCase(field.name)}` 136 | 137 | if (field.type instanceof VectorType) 138 | code.add(`${field.name}: ${this.renderVectorSerialize(`this.${toCamelCase(field.name)}`, field.type) + coma}`, 2) 139 | else if (field.type instanceof CustomType) 140 | code.add(`${fieldVar} ? this.${toCamelCase(field.name)}.serialize() : undefined${coma}`, 2) 141 | else 142 | code.add(fieldVar + coma, 2) 143 | }) 144 | 145 | 146 | code.add('}', 1) 147 | code.add('}') 148 | 149 | return code 150 | } 151 | 152 | private renderType(type: Type, withoutUndefined = false): string { 153 | if (type instanceof StringType) 154 | return 'string' 155 | 156 | if (type instanceof NumberType) 157 | return 'number' 158 | 159 | if (type instanceof AnyType) 160 | return 'any' 161 | 162 | if (type instanceof BooleanType) 163 | return 'boolean' 164 | 165 | if (type instanceof IntBoolType) 166 | return 'boolean' 167 | 168 | if (type instanceof CustomType) 169 | return type.name + `${!withoutUndefined ? '|undefined' : ''}` 170 | 171 | if (type instanceof VectorType) { 172 | return this.renderType(type.item, true) + `[]${!withoutUndefined ? '|undefined' : ''}` 173 | } 174 | 175 | throw new Error('UNSUPPORTED TYPE' + JSON.stringify(type)) 176 | } 177 | 178 | private genComa(list: any[], index: number): string { 179 | return (index == list.length - 1) ? '' : ',' 180 | } 181 | 182 | private renderVectorDeserialize(value: string, type: Type): string { 183 | let code = '' 184 | 185 | if (type instanceof VectorType) 186 | code += `${value} ? ${value}.map(v => ${this.renderVectorDeserialize('v', type.item)}) : undefined` 187 | else if (type instanceof CustomType) 188 | code += `${value} ? ${type.name}.deserialize(${value}) : undefined` 189 | else 190 | code += value 191 | 192 | return code 193 | } 194 | 195 | private renderVectorSerialize(value: string, type: Type): string { 196 | let code = '' 197 | 198 | if (type instanceof VectorType) 199 | code += `${value} ? ${value}.map(v => ${this.renderVectorSerialize('v', type.item)}) : undefined` 200 | else if (type instanceof CustomType) 201 | code += `${value}.serialize()` 202 | else 203 | code += value 204 | 205 | return code 206 | } 207 | 208 | private isCustomType(type: Type): boolean { 209 | if (type instanceof CustomType) 210 | return true 211 | if (type instanceof VectorType) 212 | return this.isCustomType(type.item) 213 | 214 | return false 215 | } 216 | 217 | private getCustomType(type: Type): CustomType | null { 218 | if (type instanceof VectorType) 219 | return this.getCustomType(type.item) 220 | if (type instanceof CustomType) 221 | return type 222 | 223 | return null 224 | } 225 | } -------------------------------------------------------------------------------- /src/infrastructure/codeGeneration/SwiftCodeGenerator.ts: -------------------------------------------------------------------------------- 1 | import {CodeGenerator} from "../../domain/codeGeneration/CodeGenerator"; 2 | import ClassScheme from "../../domain/schema/schema/ClassScheme"; 3 | import SourceCode from "../../domain/codeGeneration/SourceCode"; 4 | import StringType from "../../domain/schema/types/StringType"; 5 | import NumberType from "../../domain/schema/types/NumberType"; 6 | import AnyType from "../../domain/schema/types/AnyType"; 7 | import BooleanType from "../../domain/schema/types/BooleanType"; 8 | import CustomType from "../../domain/schema/types/CustomType"; 9 | import {Type} from "../../domain/schema/types/Type"; 10 | import VectorType from "../../domain/schema/types/VectorType"; 11 | import {toCamelCase} from "./Utils"; 12 | import IntBoolType from "../../domain/schema/types/IntBoolType"; 13 | import SourceFile from "../../domain/codeGeneration/SourceFile"; 14 | 15 | export default class SwiftCodeGenerator implements CodeGenerator { 16 | public generateClass(scheme: ClassScheme): SourceFile { 17 | let code = new SourceCode() 18 | let props = this.generateProps(scheme) 19 | let constructor = this.generateClassConstructor(scheme) 20 | let deserializeMethod = this.generateDeserializeMethod(scheme) 21 | let serializeMethod = this.generateSerializeMethod(scheme) 22 | 23 | code.add(`class ${scheme.name} {`) 24 | code.append(props) 25 | code.add('') 26 | code.append(constructor, 1) 27 | code.add('') 28 | code.append(deserializeMethod, 1) 29 | code.add('') 30 | code.append(serializeMethod, 1) 31 | code.add('}') 32 | 33 | return new SourceFile(`${scheme.name}.swift`, code) 34 | } 35 | 36 | private generateProps(scheme: ClassScheme): SourceCode { 37 | let code = new SourceCode() 38 | 39 | scheme.fields.forEach((field, index) => { 40 | code.add(`public var ${toCamelCase(field.name)}: ${this.renderType(field.type)}`, 1) 41 | }) 42 | 43 | return code 44 | } 45 | 46 | private generateClassConstructor(scheme: ClassScheme): SourceCode { 47 | let code = new SourceCode() 48 | 49 | code.add('init (') 50 | 51 | scheme.fields.forEach((field, index) => { 52 | let coma = this.genComa(scheme.fields, index) 53 | 54 | code.add(`${toCamelCase(field.name)}: ${this.renderType(field.type)}${coma}`, 1) 55 | }) 56 | 57 | code.add(') {') 58 | scheme.fields.forEach((field, index) => { 59 | code.add(`self.${toCamelCase(field.name)} = ${toCamelCase(field.name)}`, 1) 60 | }) 61 | code.add('}') 62 | 63 | return code 64 | } 65 | 66 | private generateDeserializeMethod(scheme: ClassScheme): SourceCode { 67 | let code = new SourceCode() 68 | 69 | code.add(`public static func deserialize(raw: [String: Any]?) -> ${scheme.name}? {`) 70 | code.add(`guard let raw = raw else {`, 1) 71 | code.add(`return nil`, 2) 72 | code.add(`}`, 1) 73 | code.add('') 74 | code.add(`return ${scheme.name} (`, 1) 75 | 76 | scheme.fields.forEach((field, index) => { 77 | let coma = this.genComa(scheme.fields, index) 78 | let fieldVar = `${toCamelCase(field.name)}: raw["${field.name}"] as? ${this.renderNonOptionalType(field.type)}` 79 | 80 | if (field.type instanceof VectorType) 81 | code.add(this.renderVectorDeserialize(`${toCamelCase(field.name)}: (raw["${field.name}"] as? [Any])`, field.type) + coma, 2) 82 | else if (field.type instanceof CustomType) 83 | code.add(`${toCamelCase(field.name)}: ${field.type.name}.deserialize(raw: raw["${field.name}"] as? [String : Any])${coma}`, 2) 84 | else if (field.type instanceof IntBoolType) 85 | code.add(`!!${fieldVar}${coma}`, 2) 86 | else 87 | code.add(fieldVar + coma, 2) 88 | }) 89 | 90 | code.add(`)`, 1) 91 | code.add('}') 92 | 93 | return code 94 | } 95 | 96 | private generateSerializeMethod(scheme: ClassScheme): SourceCode { 97 | let code = new SourceCode() 98 | 99 | code.add(`public func serialize() -> [String: Any] {`) 100 | 101 | code.add(`return [`, 1) 102 | 103 | 104 | scheme.fields.forEach((field, index) => { 105 | let coma = this.genComa(scheme.fields, index) 106 | let fieldVar = `"${field.name}": self.${toCamelCase(field.name)}` 107 | 108 | if (field.type instanceof VectorType) 109 | code.add(`"${field.name}": ${this.renderVectorSerialize(`self.${toCamelCase(field.name)}?`, field.type) + coma}`, 2) 110 | else if (field.type instanceof CustomType) 111 | code.add(`${fieldVar}?.serialize()${coma}`, 2) 112 | else 113 | code.add(`${fieldVar}${coma}`, 2) 114 | }) 115 | 116 | 117 | code.add(']', 1) 118 | code.add('}') 119 | 120 | return code 121 | } 122 | 123 | private renderType(type: Type, withoutUndefined = false): string { 124 | if (type instanceof StringType) 125 | return 'String?' 126 | 127 | if (type instanceof NumberType) 128 | return 'Int?' 129 | 130 | if (type instanceof AnyType) 131 | return 'Any?' 132 | 133 | if (type instanceof BooleanType) 134 | return 'Bool?' 135 | 136 | if (type instanceof IntBoolType) 137 | return 'Bool?' 138 | 139 | if (type instanceof CustomType) 140 | return type.name + `${!withoutUndefined ? '?' : ''}` 141 | 142 | if (type instanceof VectorType) { 143 | return '[' + this.renderNonOptionalType(type.item) + `]${!withoutUndefined ? '?' : ''}` 144 | } 145 | 146 | throw new Error('UNSUPPORTED TYPE' + JSON.stringify(type)) 147 | } 148 | 149 | private renderNonOptionalType(type: Type): string { 150 | if (type instanceof StringType) 151 | return 'String' 152 | 153 | if (type instanceof NumberType) 154 | return 'Int' 155 | 156 | if (type instanceof AnyType) 157 | return 'Any' 158 | 159 | if (type instanceof BooleanType) 160 | return 'Bool' 161 | 162 | if (type instanceof IntBoolType) 163 | return 'Bool' 164 | 165 | if (type instanceof CustomType) 166 | return type.name 167 | 168 | if (type instanceof VectorType) { 169 | return '[' + this.renderNonOptionalType(type.item) + ']' 170 | } 171 | 172 | throw new Error('UNSUPPORTED TYPE' + JSON.stringify(type)) 173 | } 174 | 175 | private genComa(list: any[], index: number): string { 176 | return (index == list.length - 1) ? '' : ',' 177 | } 178 | 179 | private renderVectorDeserialize(value: string, type: Type): string { 180 | let code = '' 181 | 182 | if (type instanceof VectorType) 183 | code += `${value}?.flatMap({${this.renderVectorDeserialize('$0', type.item)}})` 184 | else if (type instanceof CustomType) 185 | code += `${type.name}.deserialize(raw: ${value} as? [String: Any])` 186 | else 187 | code += `${value} as? ${this.renderNonOptionalType(type)}` 188 | 189 | return code 190 | } 191 | 192 | private renderVectorSerialize(value: string, type: Type): string { 193 | let code = '' 194 | 195 | if (type instanceof VectorType) 196 | code += `${value}.map({${this.renderVectorSerialize('$0', type.item)}})` 197 | else if (type instanceof CustomType) 198 | code += `${value}.serialize()` 199 | else 200 | code += value 201 | 202 | return code 203 | } 204 | 205 | private isCustomType(type: Type): boolean { 206 | if (type instanceof CustomType) 207 | return true 208 | if (type instanceof VectorType) 209 | return this.isCustomType(type.item) 210 | 211 | return false 212 | } 213 | 214 | private getCustomType(type: Type): CustomType | null { 215 | if (type instanceof VectorType) 216 | return this.getCustomType(type.item) 217 | if (type instanceof CustomType) 218 | return type 219 | 220 | return null 221 | } 222 | } -------------------------------------------------------------------------------- /src/infrastructure/codeGeneration/TypescriptCodeGenerator.ts: -------------------------------------------------------------------------------- 1 | import {CodeGenerator} from "../../domain/codeGeneration/CodeGenerator"; 2 | import ClassScheme from "../../domain/schema/schema/ClassScheme"; 3 | import SourceCode from "../../domain/codeGeneration/SourceCode"; 4 | import StringType from "../../domain/schema/types/StringType"; 5 | import NumberType from "../../domain/schema/types/NumberType"; 6 | import AnyType from "../../domain/schema/types/AnyType"; 7 | import BooleanType from "../../domain/schema/types/BooleanType"; 8 | import CustomType from "../../domain/schema/types/CustomType"; 9 | import {Type} from "../../domain/schema/types/Type"; 10 | import VectorType from "../../domain/schema/types/VectorType"; 11 | import {toCamelCase} from "./Utils"; 12 | import ApiMethodScheme from "../../domain/schema/schema/ApiMethodScheme"; 13 | import IntBoolType from "../../domain/schema/types/IntBoolType"; 14 | import SourceFile from "../../domain/codeGeneration/SourceFile"; 15 | 16 | export default class TypescriptCodeGenerator implements CodeGenerator { 17 | public generateClass(scheme: ClassScheme): SourceFile { 18 | let code = new SourceCode() 19 | let imports = this.generateImports(scheme) 20 | let constructor = this.generateClassConstructor(scheme) 21 | let deserializeMethod = this.generateDeserializeMethod(scheme) 22 | let serializeMethod = this.generateSerializeMethod(scheme) 23 | 24 | code.append(imports) 25 | if (imports.lines.length > 0) 26 | code.add('') 27 | code.add(`export default class ${scheme.name} {`) 28 | code.append(constructor, 1) 29 | code.add('') 30 | code.append(deserializeMethod, 1) 31 | code.add('') 32 | code.append(serializeMethod, 1) 33 | code.add('}') 34 | 35 | return new SourceFile(`${scheme.name}.ts`, code) 36 | } 37 | 38 | public generateApiMethod(scheme: ApiMethodScheme): SourceCode { 39 | let code = new SourceCode() 40 | 41 | let methodName = toCamelCase(scheme.name, false, '.') 42 | let propsName = `MethodsProps.${toCamelCase(scheme.name, true, '.')}Params` 43 | let responseName = this.renderType(scheme.responseType, true) 44 | 45 | 46 | /** 47 | * Returns detailed information on users. 48 | * 49 | * 50 | * @param {{ 51 | * subview:string, 52 | * el:(number|Element) 53 | * }} params 54 | */ 55 | 56 | code.add(`/**`) 57 | code.add(` * ${scheme.description}`) 58 | code.add(' *') 59 | code.add(' * @param {{') 60 | scheme.params.forEach((param, index) => { 61 | let coma = this.genComa(scheme.params, index) 62 | 63 | code.add(` * ${toCamelCase(param.name)}: (${this.renderType(param.type, true)}${param.required ? '' : '|undefined'})${coma}`) 64 | }) 65 | code.add(' * }} params') 66 | code.add(' *') 67 | code.add(` * @returns {Promise}`) 68 | code.add(` */`) 69 | code.add(`public async ${methodName}(params: ${propsName}): Promise {`) 70 | code.add('return this.call(', 1) 71 | code.add(`'${scheme.name}',`, 2) 72 | code.add(`{`, 2) 73 | scheme.params.forEach((param, index) => { 74 | let coma = this.genComa(scheme.params, index) 75 | 76 | code.add(`${param.name}: params.${toCamelCase(param.name)}${coma}`, 3) 77 | }) 78 | code.add(`},`, 2) 79 | code.add(`Responses.${responseName}`, 2) 80 | code.add(')', 1) 81 | code.add('}') 82 | 83 | return code 84 | } 85 | 86 | public generateApiMethodParamsInterface(scheme: ApiMethodScheme): SourceCode { 87 | let code = new SourceCode() 88 | 89 | code.add(`export interface ${toCamelCase(scheme.name, true, '.')}Params {`) 90 | 91 | scheme.params.forEach((prop, index) => { 92 | let coma = this.genComa(scheme.params, index) 93 | let isCustom = this.isCustomType(prop.type) 94 | 95 | code.add(`/**`, 1) 96 | code.add(` * ${prop.description}`, 1) 97 | code.add(` */`, 1) 98 | code.add(`${toCamelCase(prop.name)}${prop.required ? '' : '?'}: ${isCustom ? 'Models.' : ''}${this.renderType(prop.type, true)}${coma}`, 1) 99 | }) 100 | 101 | code.add('}') 102 | 103 | return code 104 | } 105 | 106 | private generateImports(scheme: ClassScheme): SourceCode { 107 | let code = new SourceCode() 108 | 109 | scheme.fields.forEach((field, index) => { 110 | let customType = this.getCustomType(field.type) 111 | 112 | if (customType) { 113 | code.add(`import ${customType.name} from './${customType.name}'`) 114 | } 115 | }) 116 | 117 | return code 118 | } 119 | 120 | private generateClassConstructor(scheme: ClassScheme): SourceCode { 121 | let code = new SourceCode() 122 | let jsdoc = this.generateClassConstructorJSDoc(scheme) 123 | 124 | code.append(jsdoc) 125 | 126 | code.add('constructor (') 127 | 128 | scheme.fields.forEach((field, index) => { 129 | let coma = this.genComa(scheme.fields, index) 130 | 131 | code.add(`readonly ${toCamelCase(field.name)}: ${this.renderType(field.type)}${coma}`, 1) 132 | }) 133 | 134 | code.add(') {') 135 | code.add('') 136 | code.add('}') 137 | 138 | return code 139 | } 140 | 141 | private generateClassConstructorJSDoc(scheme: ClassScheme): SourceCode { 142 | let code = new SourceCode() 143 | 144 | code.add('/**') 145 | code.add(' * @class') 146 | 147 | scheme.fields.forEach(field => { 148 | code.add(` * @property {${this.renderType(field.type)}} ${toCamelCase(field.name)} ${field.description}`) 149 | }) 150 | 151 | code.add(' */') 152 | 153 | return code 154 | } 155 | 156 | private generateDeserializeMethod(scheme: ClassScheme): SourceCode { 157 | let code = new SourceCode() 158 | 159 | code.add('/**') 160 | code.add(' * @param {Object} raw') 161 | code.add(` * @returns {${scheme.name}}`) 162 | code.add(' */') 163 | 164 | code.add(`static deserialize(raw: any): ${scheme.name} {`) 165 | code.add(`return new ${scheme.name} (`, 1) 166 | 167 | scheme.fields.forEach((field, index) => { 168 | let coma = this.genComa(scheme.fields, index) 169 | let fieldVar = `raw['${field.name}']` 170 | 171 | if (field.type instanceof VectorType) 172 | code.add(this.renderVectorDeserialize(fieldVar, field.type) + coma, 2) 173 | else if (field.type instanceof CustomType) 174 | code.add(`${fieldVar} ? ${field.type.name}.deserialize(${fieldVar}) : undefined${coma}`, 2) 175 | else if (field.type instanceof IntBoolType) 176 | code.add(`!!${fieldVar}${coma}`, 2) 177 | else 178 | code.add(fieldVar + coma, 2) 179 | }) 180 | 181 | code.add(`)`, 1) 182 | code.add('}') 183 | 184 | return code 185 | } 186 | 187 | private generateSerializeMethod(scheme: ClassScheme): SourceCode { 188 | let code = new SourceCode() 189 | 190 | code.add('/**') 191 | code.add(` * @returns {Object}`) 192 | code.add(' */') 193 | 194 | code.add(`public serialize(): Object {`) 195 | 196 | code.add(`return {`, 1) 197 | 198 | 199 | scheme.fields.forEach((field, index) => { 200 | let coma = this.genComa(scheme.fields, index) 201 | let fieldVar = `${field.name}: this.${toCamelCase(field.name)}` 202 | 203 | if (field.type instanceof VectorType) 204 | code.add(`${field.name}: ${this.renderVectorSerialize(`this.${toCamelCase(field.name)}`, field.type) + coma}`, 2) 205 | else if (field.type instanceof CustomType) 206 | code.add(`${fieldVar} ? this.${toCamelCase(field.name)}.serialize() : undefined${coma}`, 2) 207 | else 208 | code.add(fieldVar + coma, 2) 209 | }) 210 | 211 | 212 | code.add('}', 1) 213 | code.add('}') 214 | 215 | return code 216 | } 217 | 218 | private renderType(type: Type, withoutUndefined = false): string { 219 | if (type instanceof StringType) 220 | return 'string' 221 | 222 | if (type instanceof NumberType) 223 | return 'number' 224 | 225 | if (type instanceof AnyType) 226 | return 'any' 227 | 228 | if (type instanceof BooleanType) 229 | return 'boolean' 230 | 231 | if (type instanceof IntBoolType) 232 | return 'boolean' 233 | 234 | if (type instanceof CustomType) 235 | return type.name + `${!withoutUndefined ? '|undefined' : ''}` 236 | 237 | if (type instanceof VectorType) { 238 | return this.renderType(type.item, true) + `[]${!withoutUndefined ? '|undefined' : ''}` 239 | } 240 | 241 | throw new Error('UNSUPPORTED TYPE' + JSON.stringify(type)) 242 | } 243 | 244 | private genComa(list: any[], index: number): string { 245 | return (index == list.length - 1) ? '' : ',' 246 | } 247 | 248 | private renderVectorDeserialize(value: string, type: Type): string { 249 | let code = '' 250 | 251 | if (type instanceof VectorType) 252 | code += `${value} ? ${value}.map((v: any) => ${this.renderVectorDeserialize('v', type.item)}) : undefined` 253 | else if (type instanceof CustomType) 254 | code += `${value} ? ${type.name}.deserialize(${value}) : undefined` 255 | else 256 | code += value 257 | 258 | return code 259 | } 260 | 261 | private renderVectorSerialize(value: string, type: Type): string { 262 | let code = '' 263 | 264 | if (type instanceof VectorType) 265 | code += `${value} ? ${value}.map((v: any) => ${this.renderVectorSerialize('v', type.item)}) : undefined` 266 | else if (type instanceof CustomType) 267 | code += `${value}.serialize()` 268 | else 269 | code += value 270 | 271 | return code 272 | } 273 | 274 | private isCustomType(type: Type): boolean { 275 | if (type instanceof CustomType) 276 | return true 277 | if (type instanceof VectorType) 278 | return this.isCustomType(type.item) 279 | 280 | return false 281 | } 282 | 283 | private getCustomType(type: Type): CustomType | null { 284 | if (type instanceof VectorType) 285 | return this.getCustomType(type.item) 286 | if (type instanceof CustomType) 287 | return type 288 | 289 | return null 290 | } 291 | } -------------------------------------------------------------------------------- /src/infrastructure/codeGeneration/Utils.ts: -------------------------------------------------------------------------------- 1 | export function toCamelCase(str: string, capitalize: boolean = false, separator = '_') { 2 | const parts = str.split(separator) 3 | 4 | if (!parts.length) return str 5 | 6 | const capitalized = parts.slice(1).map(part => part[0].toUpperCase() + part.substr(1)) 7 | 8 | capitalized.unshift(parts[0]) 9 | 10 | let result = capitalized.join('') 11 | 12 | if (capitalize) 13 | return result[0].toUpperCase() + result.slice(1) 14 | 15 | return result 16 | } -------------------------------------------------------------------------------- /src/infrastructure/logger/ConsoleLogger.ts: -------------------------------------------------------------------------------- 1 | import {format, inspect} from 'util' 2 | import BaseLogger from '../../domain/logger/BaseLogger' 3 | 4 | class ColorCodes { 5 | static RED = '\x1b[31m' 6 | static CYAN = '\x1b[36m' 7 | static YELLOW = '\x1b[33m' 8 | static RESET = '\x1b[0m' 9 | } 10 | 11 | export default class ConsoleLogger implements BaseLogger { 12 | public log(prefix: string, data?: any, recursiveDepth?: boolean) { 13 | this.prepareLog(ColorCodes.CYAN, 'log', prefix, data, recursiveDepth) 14 | } 15 | 16 | public warn(prefix: string, data?: any, recursiveDepth?: boolean) { 17 | this.prepareLog(ColorCodes.YELLOW, 'warn', prefix, data, recursiveDepth) 18 | } 19 | 20 | public error(prefix: string, data?: any, recursiveDepth?: boolean) { 21 | this.prepareLog(ColorCodes.RED, 'error', prefix, data, recursiveDepth) 22 | } 23 | 24 | private prepareLog( 25 | color: ColorCodes, 26 | tag: string, 27 | prefix: string, 28 | data?: any, 29 | recursiveDepth?: boolean 30 | ) { 31 | const time = new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1') 32 | 33 | if (data && recursiveDepth == true) { 34 | process.stdout.write(`${time} ${color}[${tag}]${ColorCodes.RESET} ${prefix} ${(inspect as any)(data || '', { depth: null })} \n`) 35 | 36 | return 37 | } 38 | 39 | process.stdout.write(`${time} ${color}[${tag}]${ColorCodes.RESET} ${prefix} ${format(data || '')} \n`) 40 | } 41 | } -------------------------------------------------------------------------------- /src/infrastructure/schemeGeneration/JSONSchemeGenerator.ts: -------------------------------------------------------------------------------- 1 | import {SchemeGenerator} from "../../domain/schemeGeneration/SchemeGenerator"; 2 | import ClassScheme from "../../domain/schema/schema/ClassScheme"; 3 | import JSONSchemeSource from "./sources/JSONSchemeSource"; 4 | import {Type} from "../../domain/schema/types/Type"; 5 | import CustomType from "../../domain/schema/types/CustomType"; 6 | import VectorType from "../../domain/schema/types/VectorType"; 7 | import NumberType from "../../domain/schema/types/NumberType"; 8 | import StringType from "../../domain/schema/types/StringType"; 9 | import BooleanType from "../../domain/schema/types/BooleanType"; 10 | import AnyType from "../../domain/schema/types/AnyType"; 11 | import ClassField from "../../domain/schema/schema/ClassField"; 12 | import {toCamelCase} from "../codeGeneration/Utils"; 13 | 14 | export default class JSONSchemeGenerator extends SchemeGenerator { 15 | public generate(source: JSONSchemeSource): ClassScheme[] { 16 | return this.json2scheme(source.jsonData) 17 | } 18 | 19 | private json2scheme( 20 | json: Object, 21 | name: string = 'Root', 22 | schemesByName: { [key: string]: ClassScheme } = {} 23 | ): ClassScheme[] { 24 | let schemes: ClassScheme[] = [] 25 | let fields: ClassField[] = [] 26 | 27 | for (let key in json) { 28 | let value = (json as any)[key] 29 | 30 | let type = this.genTypeForPrimitive((json as any)[key], toCamelCase(key, true)) 31 | 32 | let customType = this.hasCustomType(type, value) 33 | 34 | if (customType) { 35 | let customTypeSchemes: ClassScheme[] 36 | 37 | if (value instanceof Array) { 38 | customTypeSchemes = this.json2scheme(customType.value, toCamelCase(key, true), schemesByName) 39 | } 40 | else 41 | customTypeSchemes = this.json2scheme(value, toCamelCase(key, true), schemesByName) 42 | 43 | // get scheme for current key (its the last one) 44 | let scheme = customTypeSchemes[customTypeSchemes.length - 1] 45 | // remove last scheme 46 | customTypeSchemes = customTypeSchemes.slice(0, customTypeSchemes.length - 1) 47 | 48 | if (schemesByName[scheme.name]) { 49 | if (!this.isClassSchemesEqual(schemesByName[scheme.name], scheme)) { 50 | // if we had that name and schemes are not the same -> we change name 51 | let newName = toCamelCase(key + this.getRandomInt(1, 1000), true) 52 | 53 | customTypeSchemes.push(new ClassScheme(newName, scheme.fields)) 54 | type = this.genTypeForPrimitive((json as any)[key], newName) 55 | } 56 | } 57 | else 58 | customTypeSchemes.push(scheme) 59 | 60 | schemesByName[scheme.name] = scheme 61 | 62 | schemes.push(...customTypeSchemes) 63 | } 64 | 65 | fields.push( 66 | new ClassField( 67 | key, 68 | type, 69 | '' 70 | ) 71 | ) 72 | } 73 | 74 | schemes.push(new ClassScheme(name, fields)) 75 | 76 | return schemes 77 | } 78 | 79 | private genTypeForPrimitive(value: any, name: string): Type { 80 | let type = typeof value 81 | 82 | if (type == 'string') 83 | return new StringType() 84 | if (type == 'number') 85 | return new NumberType() 86 | if (type == 'boolean') 87 | return new BooleanType() 88 | if (value == null || value == undefined) 89 | return new AnyType() 90 | if (value instanceof Array) { 91 | if (value.length == 0) 92 | return new VectorType(new AnyType()) 93 | 94 | let isUniform = true 95 | let prevType: Type|null = null 96 | let prevItem: any|null 97 | 98 | for (let item of value) { 99 | let type = this.genTypeForPrimitive(item, name) 100 | 101 | if (prevType && !this.isTypeEqual(type, prevType)) 102 | isUniform = false 103 | 104 | if ( 105 | prevItem && 106 | type instanceof CustomType && 107 | !this.isObjectsEqual(prevItem, item) 108 | ) 109 | isUniform = false 110 | 111 | prevType = type 112 | prevItem = item 113 | } 114 | 115 | if (isUniform) 116 | return new VectorType(prevType as Type) 117 | 118 | return new VectorType(new AnyType()) 119 | } 120 | if (value instanceof Object) { 121 | return new CustomType(name) 122 | } 123 | 124 | throw new Error('Unknown type for value:' + value) 125 | } 126 | 127 | private isTypeEqual(firstType: Type, secondType: Type): boolean { 128 | if ( 129 | firstType instanceof NumberType && 130 | secondType instanceof NumberType 131 | ) 132 | return true 133 | 134 | if ( 135 | firstType instanceof StringType && 136 | secondType instanceof StringType 137 | ) 138 | return true 139 | 140 | if ( 141 | firstType instanceof BooleanType && 142 | secondType instanceof BooleanType 143 | ) 144 | return true 145 | 146 | if ( 147 | firstType instanceof AnyType && 148 | secondType instanceof AnyType 149 | ) 150 | return true 151 | 152 | if ( 153 | firstType instanceof VectorType && 154 | secondType instanceof VectorType 155 | ) { 156 | return this.isTypeEqual(firstType.item, secondType.item) 157 | } 158 | 159 | if ( 160 | firstType instanceof CustomType && 161 | secondType instanceof CustomType 162 | ) 163 | return firstType.name == secondType.name 164 | 165 | return false 166 | } 167 | 168 | private isObjectsEqual(firstObject: object, secondObject: object): boolean { 169 | let firstObjectKeys = Object.keys(firstObject) 170 | let secondObjectKeys = Object.keys(secondObject) 171 | 172 | if (firstObjectKeys.length != secondObjectKeys.length) 173 | return false 174 | 175 | for (let key in firstObjectKeys) { 176 | let firstObjectKey = firstObjectKeys[key] 177 | let secondObjectKey = secondObjectKeys[key] 178 | 179 | if (firstObjectKey !== secondObjectKey) 180 | return false 181 | 182 | let firstType = this.genTypeForPrimitive((firstObject as any)[firstObjectKey], firstObjectKey) 183 | let secondType = this.genTypeForPrimitive((secondObject as any)[secondObjectKey], secondObjectKey) 184 | 185 | if (!this.isTypeEqual(firstType, secondType)) 186 | return false 187 | } 188 | 189 | return true 190 | } 191 | 192 | private isClassSchemesEqual(firstClassScheme: ClassScheme, secondClassScheme: ClassScheme): boolean { 193 | if (firstClassScheme.name !== secondClassScheme.name) 194 | return false 195 | 196 | if (firstClassScheme.fields.length !== secondClassScheme.fields.length) 197 | return false 198 | 199 | for (let i in firstClassScheme.fields) { 200 | if (firstClassScheme.fields[i].name !== secondClassScheme.fields[i].name) 201 | return false 202 | 203 | if ( 204 | !this.isTypeEqual( 205 | firstClassScheme.fields[i].type, 206 | secondClassScheme.fields[i].type 207 | ) 208 | ) 209 | return false 210 | } 211 | 212 | return true 213 | } 214 | 215 | private hasCustomType(type: Type, value: any): { type: CustomType, value: any } | null { 216 | if (type instanceof VectorType) 217 | return this.hasCustomType(type.item, value[0]) 218 | if (type instanceof CustomType) 219 | return { type, value } 220 | 221 | return null 222 | } 223 | 224 | private getRandomInt(min: number, max: number): number { 225 | return Math.floor(Math.random() * (max - min)) + min; 226 | } 227 | } -------------------------------------------------------------------------------- /src/infrastructure/schemeGeneration/sources/JSONSchemeSource.ts: -------------------------------------------------------------------------------- 1 | import {SchemeSource} from "../../../domain/schemeGeneration/SchemeSource"; 2 | 3 | export default class JSONSchemeSource implements SchemeSource { 4 | constructor( 5 | readonly jsonData: object 6 | ) { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /src/presentation/BaseConsoleApplication.ts: -------------------------------------------------------------------------------- 1 | import {writeFile, readFile, readdir} from "fs"; 2 | 3 | export default class BaseConsoleApplication { 4 | protected env = process.env['NODE_ENV'] || 'development' 5 | protected isProducton = this.env == 'production' 6 | 7 | protected die(error: string) { 8 | console.error(error) 9 | process.exit() 10 | } 11 | 12 | protected getArgs() { 13 | let result: { [key: string]: string } = {} 14 | 15 | process.argv.forEach(arg => { 16 | let test = arg.split('=') 17 | 18 | if (test[0] && test[1] && test[0][0] == '-') { 19 | result[test[0].replace('-', '')] = test[1] 20 | } 21 | }) 22 | 23 | return result 24 | } 25 | 26 | protected async saveToFile(name: string, data: string): Promise { 27 | return new Promise((resolve, reject) => { 28 | writeFile(name, data, err => { 29 | if (err) { 30 | reject(err) 31 | return 32 | } 33 | 34 | resolve() 35 | }) 36 | }) 37 | } 38 | 39 | protected async readFile(name: string): Promise { 40 | return new Promise((resolve, reject) => { 41 | readFile(name, 'utf-8', (err, data) => { 42 | if (err) { 43 | reject(err) 44 | return 45 | } 46 | 47 | resolve(data) 48 | }) 49 | }) 50 | } 51 | 52 | protected async readDir(dir: string): Promise { 53 | return new Promise((resolve, reject) => { 54 | readdir(dir, (err, files) => { 55 | if (err) { 56 | reject(err) 57 | return 58 | } 59 | 60 | resolve(files) 61 | }) 62 | }) 63 | } 64 | } -------------------------------------------------------------------------------- /src/presentation/ModelifyApplication.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import BaseConsoleApplication from "./BaseConsoleApplication"; 4 | import ConsoleLogger from "../infrastructure/logger/ConsoleLogger"; 5 | import JavaScriptCodeGenerator from "../infrastructure/codeGeneration/JavaScriptCodeGenerator"; 6 | import {CodeGenerator} from "../domain/codeGeneration/CodeGenerator"; 7 | import TypescriptCodeGenerator from "../infrastructure/codeGeneration/TypescriptCodeGenerator"; 8 | import SwiftCodeGenerator from "../infrastructure/codeGeneration/SwiftCodeGenerator"; 9 | import JSONSchemeGenerator from "../infrastructure/schemeGeneration/JSONSchemeGenerator"; 10 | import JSONSchemeSource from "../infrastructure/schemeGeneration/sources/JSONSchemeSource"; 11 | import {join} from "path"; 12 | import Modelify from "../application/Modelify"; 13 | 14 | class ModelifyApplication extends BaseConsoleApplication { 15 | constructor() { 16 | super() 17 | 18 | const args = this.getArgs() 19 | 20 | const lang = args.lang 21 | const input = args.i 22 | const output = args.o 23 | 24 | if (!lang || !input || !output) 25 | this.die('\nWrong args\n\n' + 26 | 'Usage: modelify -lang=js -i=/path/to/your.json -o=/path/to/out/dir\n\n' + 27 | 'Available languages: js, ts, swift' 28 | ) 29 | 30 | this.run(lang, input, output) 31 | } 32 | 33 | async run( 34 | lang: string, 35 | input: string, 36 | output: string 37 | ) { 38 | let logger = new ConsoleLogger() 39 | let json = JSON.parse(await this.readFile(input)) 40 | let codeGenerator = this.getCodeGeneratorByLang(lang) 41 | let schemeGenerator = new JSONSchemeGenerator() 42 | let modelify = new Modelify(schemeGenerator, codeGenerator, logger) 43 | 44 | let files = modelify.generate(new JSONSchemeSource(json)) 45 | 46 | for (let file of files) { 47 | await this.saveToFile(join(output, file.fileName),file.code.render()) 48 | } 49 | 50 | console.log('Done!') 51 | } 52 | 53 | private getCodeGeneratorByLang(lang: string): CodeGenerator { 54 | if (lang === 'js') 55 | return new JavaScriptCodeGenerator() 56 | if (lang === 'ts') 57 | return new TypescriptCodeGenerator() 58 | if (lang === 'swift') 59 | return new SwiftCodeGenerator() 60 | 61 | throw new Error('No code generator for ' + lang) 62 | } 63 | } 64 | 65 | new ModelifyApplication() -------------------------------------------------------------------------------- /src/test/JSONSchemeGeneratorTest.ts: -------------------------------------------------------------------------------- 1 | import JSONSchemeGenerator from "../infrastructure/schemeGeneration/JSONSchemeGenerator"; 2 | import JSONSchemeSource from "../infrastructure/schemeGeneration/sources/JSONSchemeSource"; 3 | import {deepStrictEqual} from "assert"; 4 | import ClassScheme from "../domain/schema/schema/ClassScheme"; 5 | import ClassField from "../domain/schema/schema/ClassField"; 6 | import NumberType from "../domain/schema/types/NumberType"; 7 | import StringType from "../domain/schema/types/StringType"; 8 | import AnyType from "../domain/schema/types/AnyType"; 9 | import BooleanType from "../domain/schema/types/BooleanType"; 10 | import CustomType from "../domain/schema/types/CustomType"; 11 | import VectorType from "../domain/schema/types/VectorType"; 12 | 13 | let schemeGenerator = new JSONSchemeGenerator() 14 | 15 | export function testBaseTypes() { 16 | let source = new JSONSchemeSource({ 17 | a: 1, 18 | b: '2', 19 | c: true, 20 | d: null, 21 | e: undefined 22 | }) 23 | 24 | let generatedScheme = schemeGenerator.generate(source) 25 | let expectedScheme = [ 26 | new ClassScheme( 27 | 'Root', 28 | [ 29 | new ClassField('a', new NumberType(), ''), 30 | new ClassField('b', new StringType(), ''), 31 | new ClassField('c', new BooleanType(), ''), 32 | new ClassField('d', new AnyType(), ''), 33 | new ClassField('e', new AnyType(), '') 34 | ] 35 | ) 36 | ] 37 | 38 | deepStrictEqual(expectedScheme, generatedScheme) 39 | } 40 | 41 | export function testCustomTypes() { 42 | let source = new JSONSchemeSource({ 43 | a: { 44 | a: 1, 45 | b: '2', 46 | c: true, 47 | d: null, 48 | e: undefined 49 | } 50 | }) 51 | 52 | let generatedScheme = schemeGenerator.generate(source) 53 | let expectedScheme = [ 54 | new ClassScheme( 55 | 'A', 56 | [ 57 | new ClassField('a', new NumberType(), ''), 58 | new ClassField('b', new StringType(), ''), 59 | new ClassField('c', new BooleanType(), ''), 60 | new ClassField('d', new AnyType(), ''), 61 | new ClassField('e', new AnyType(), '') 62 | ] 63 | ), 64 | 65 | new ClassScheme( 66 | 'Root', 67 | [ 68 | new ClassField('a', new CustomType('A'), '') 69 | ] 70 | ) 71 | ] 72 | 73 | deepStrictEqual(expectedScheme, generatedScheme) 74 | } 75 | 76 | export function testVectorType() { 77 | let source = new JSONSchemeSource({ 78 | a: { 79 | a: [1], 80 | b: ['2'], 81 | c: [true], 82 | d: [null], 83 | e: [undefined], 84 | f: [{ a: 1 }] 85 | } 86 | }) 87 | 88 | let generatedScheme = schemeGenerator.generate(source) 89 | let expectedScheme = [ 90 | new ClassScheme( 91 | 'F', 92 | [ 93 | new ClassField('a', new NumberType(), '') 94 | ] 95 | ), 96 | 97 | new ClassScheme( 98 | 'A', 99 | [ 100 | new ClassField('a', new VectorType(new NumberType()), ''), 101 | new ClassField('b', new VectorType(new StringType()), ''), 102 | new ClassField('c', new VectorType(new BooleanType()), ''), 103 | new ClassField('d', new VectorType(new AnyType()), ''), 104 | new ClassField('e', new VectorType(new AnyType()), ''), 105 | new ClassField('f', new VectorType(new CustomType('F')), '') 106 | ] 107 | ), 108 | 109 | new ClassScheme( 110 | 'Root', 111 | [ 112 | new ClassField('a', new CustomType('A'), '') 113 | ] 114 | ) 115 | ] 116 | 117 | deepStrictEqual(expectedScheme, generatedScheme) 118 | } -------------------------------------------------------------------------------- /src/test/Run.ts: -------------------------------------------------------------------------------- 1 | import {testBaseTypes, testCustomTypes, testVectorType} from "./JSONSchemeGeneratorTest"; 2 | 3 | testBaseTypes() 4 | 5 | testCustomTypes() 6 | 7 | testVectorType() -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "typeRoots" : ["node_modules/@types"], 5 | "outDir": "build/", 6 | "sourceMap": true, 7 | "target": "es6", 8 | "module": "commonjs", 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "types": ["node"], 12 | "allowJs": true, 13 | "strictNullChecks": true, 14 | "alwaysStrict": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true 17 | }, 18 | "include": [ 19 | "src/**/*" 20 | ], 21 | "exclude": [ 22 | "node_modules", 23 | "**/*.spec.ts" 24 | ] 25 | } --------------------------------------------------------------------------------