Skip to content

Batch mode for generating interfaces for several JSON-schemas at once #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bcherny opened this issue Sep 3, 2016 · 12 comments
Closed

Comments

@bcherny
Copy link
Owner

bcherny commented Sep 3, 2016

To avoid generating the same interface twice.

#15 (comment)

@erykpiast
Copy link
Contributor

So how the API may look like? json2ts -i *.json -o ???. Should we allow to specify some kind of renaming pattern (like sed syntax) or just concatenate all interfaces to one ts file when output file is specified and create [orig_name_wo_extension].d.ts files in case it isn't?

@bcherny
Copy link
Owner Author

bcherny commented Feb 28, 2017

Glob syntax sounds good to me.

I would separate the work into 2 parts:

  • Part 1 (easy) - each input file maps to an output file (user can take care of concating), no optimization (TS takes care of type merging, so duplicates are fine)
  • Part 2 (harder) - each input file maps to an output file, common types are pulled out into a common file to avoid duplication

@erykpiast
Copy link
Contributor

I like the split 👍.

So, for the first step, results would be like

$ ls *.json
a.json b.json c.json
$ json2ts *.json
$ ls *.d.ts
a.d.ts b.d.ts c.d.ts

no matter if a b or c contain different or equal interfaces, right?

How do we want to prevent people from specifying output file for multiple inputs? Just throw an error?

@bcherny
Copy link
Owner Author

bcherny commented Feb 28, 2017

Exactly. Error or warning is probably fine. I agree error might be better to keep the API as strong as possible.

@tomekp
Copy link

tomekp commented Jan 13, 2018

I expect a little different behavior:
I want to be able to generate multiple *.ts files, one for each defined interface (from each schema). So if some type would be generated many times it would just overwrite the same file.

It seems I'd need to add a function to generator that returns a dictionary like {"file name for type": "file content"} (+ change all declareXxx functions to return an array of strings instead of returning concatenated string, and figure out imports).

The tricky part seems to be generating file names (for SomeType in schema I want to get some-type.model.ts file) so I'd expect user to provide file name generating function.

If that's OK for you I'd start working on a PR providing that.

@bcherny
Copy link
Owner Author

bcherny commented Jan 13, 2018

@tomekp I'm not convinced that approach is better. Ostensibly if someone has already taken the trouble to put, say, schemas A B and C in JSON-Schema file X and D E and F in JSON-Schema file Y, for their case this is a natural way to modularize and group their schemas. So when we generate TypeScript code from those schemas, it seems natural to put schemas A B and C in X.d.ts and D E and F in Y.d.ts.

If a user wanted to generate 1 file per schema, all they would need to do is feed in one file per schema (a trivial refactoring of their JSON-Schemas).

On the other hand, under your scheme a user couldn't do the opposite, and generate one file containing several schemas by feeding in one file with several schemas.

What do you think?

@bytesnz
Copy link

bytesnz commented Feb 15, 2018

This is a kind of related idea. I was just looking at using this and z-schema for the schemas/typings in one of my projects. I think it is handy to support passing schemas as an array to compile so that $refs can be supported between the schemas given in the array. The functionality would be the same as passing the multiple files to json2ts I would think, apart from outputting a single string.

Edit: Having just looked at the dereferencing, it would require a bit of work as well to support passing other schemas to it in an array

@eweap
Copy link

eweap commented Mar 2, 2018

Hi @bytesnz @bcherny, You can take a look to my fork (https://github.com/eweap/json-schema-to-typescript/tree/compile-dir)

I think i have partially achieved the part 1 of the solution.

I made these modifications urgently in order to be able to use the concatenated output in our projects. But I think it's a good start.

I have not made a pull request at the moment for lack of time and because I struggled with the Jest tests (which I had never used) but I think I will finish a day when I will have more time.

Below is an overview of how I use compileDir to build final index.d.ts output:

import { outputFileSync } from 'fs-extra';
import { compileFromDir } from 'json-schema-to-typescript';

import { cleanDirectory, compilationOptions } from './utils';

const schemasDir = 'schemas';
const typesDir = 'typings';
const indexFile = 'index.d.ts';
const indexPath = `${typesDir}/${indexFile}`;
const exportFile = 'export.d.ts';
const exportPath = `${typesDir}/${exportFile}`;

export function buildTypes(): Promise<any> {
    // Clean the typings directory
    cleanDirectory(typesDir);

    // When compilation is finished
    return compileFromDir(schemasDir, compilationOptions).then(
        definitions => {
            // Build a global definition files
            const globalOutput = definitions.map(definition => definition.definition);
            outputFileSync(indexPath, globalOutput.join('\n'));

            // Generate an index file
            const exportFileOutput = definitions.map(definition => {
                // Remove root directory
                let exportPath = definition.filepath.replace(schemasDir, '.');

                // Remove extension for export
                exportPath = exportPath.replace('.json', '');

                return `export * from '${exportPath}';`;
            });
            outputFileSync(exportPath, exportFileOutput.join('\n'));

            // Generate the definitions files
            return definitions.map(definition => {
                let filepath = definition.filepath.replace(`${schemasDir}/`, '');
                // Remove extension
                filepath = filepath.replace('.json', '.d.ts');

                // Retrieve definition output
                const definitionOutput = definition.definition;

                const definitionPath = `${typesDir}/${filepath}`;

                outputFileSync(definitionPath, definitionOutput);
            });
        },
        // Failed
        error => console.error(error)
    );
}
import { existsSync, mkdirSync, removeSync } from 'fs-extra';
import { Options } from 'json-schema-to-typescript';

// Make sure a directory exists and is empty
export function cleanDirectory(dir: string, destructive = true): void {
    if (existsSync(dir)) {
        if (destructive) {
            removeSync(dir);
        } else {
            throw new Error(`'${dir}' directory exists and should not be deleted (non-destructive call)`);
        }
    }
    mkdirSync(dir);
}

export const compilationOptions: Partial<Options> = {
    bannerComment: '',
    declareExternallyReferenced: false,
    enableConstEnums: true,
    style: {
        semi: true,
        tabWidth: 4,
        singleQuote: true,
        trailingComma: 'es5',
        bracketSpacing: true,
    },
    unreachableDefinitions: true,
};

Hope it helps (And thx @bcherny for the good work !)

@Leeingnyo
Copy link

Hi, Please see following example.

Album.schema.json

{
  "$schema": "http://json-schema.org/schema#",
  "id": "Album.schema.json",
  "title": "Album",
  "required": [
    "name"
  ],
  "allOf": [
    { "$ref": "Item.schema.json" },
    {
      "properties": {
        "description": {
          "type": "object",
          "$ref": "Description.schema.json"
        },
        "name": { "type": "string" },
        "releaseDate": {
          "type": "string",
          "format": "date-time"
        }
      }
    }
  ]
}

Above one converts to below now (I editted something manually).

Album.d.ts

export type Album = Item & {
  description?: Description;
  name?: string;
  releaseDate?: string;
  [k: string]: any;
};
export interface Item = {
  type?: string;
  [k: string]: any;
};
export interface Description = {
  content?: string;
  [k: string]: any;
};

I suggest this form.

Album.d.ts

import { Item } from 'Item.d.ts';
import { Description } from 'Description.d.ts';

export type Album = Item & {
  description?: Description;
  name?: string;
  releaseDate?: string;
  [k: string]: any;
};

Traversing AST one depth only, generate ECMAScript module import statements for type dependency.
We can reuse types.

@Ethan-Arrowood
Copy link
Contributor

I just opened a PR that should resolve this issue.

@Ethan-Arrowood
Copy link
Contributor

Because of stagnation I've published a npm module that enables this feature, api only for now. https://www.npmjs.com/package/compile-schemas-to-typescript

@bcherny bcherny closed this as completed Jul 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants
@eweap @erykpiast @bytesnz @bcherny @tomekp @Leeingnyo @Ethan-Arrowood and others