Skip to content

Commit

Permalink
additional properties
Browse files Browse the repository at this point in the history
  • Loading branch information
pyramation committed Apr 13, 2024
1 parent 012057c commit ea19b59
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 4 deletions.
9 changes: 9 additions & 0 deletions __fixtures__/output/memo.camel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface Memo {
schema?: string;
memoKeys: {
key: string;
description: string;
gitRepo: string;
memo: any;
}[];
}
9 changes: 9 additions & 0 deletions __fixtures__/output/memo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface Memo {
$schema?: string;
memo_keys: {
key: string;
description: string;
git_repo: string;
memo: any;
}[];
}
1 change: 1 addition & 0 deletions __fixtures__/schemas/memo.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "memo",
"type": "object",
"required": [
"memo_keys"
Expand Down
19 changes: 19 additions & 0 deletions __tests__/__snapshots__/additional-props.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`additional 1`] = `
"export interface Person {
firstName: string;
lastName: string;
age?: number;
[key: string]: string;
}"
`;

exports[`additional 2`] = `
"export interface Person {
firstName: string;
[key: string]: {
newProp: any;
};
}"
`;
25 changes: 25 additions & 0 deletions __tests__/__snapshots__/memo.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`memo 1`] = `
"export interface Memo {
$schema?: string;
memo_keys: {
key: string;
description: string;
git_repo: string;
memo: any;
}[];
}"
`;

exports[`memo camelCase 1`] = `
"export interface Memo {
schema?: string;
memoKeys: {
key: string;
description: string;
gitRepo: string;
memo: any;
}[];
}"
`;
47 changes: 47 additions & 0 deletions __tests__/additional-props.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { generateTypeScript } from '../src';

it('additional', () => {
expect(generateTypeScript({
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"age": {
"type": "integer"
}
},
"required": ["firstName", "lastName"],
"additionalProperties": true
} as any)).toMatchSnapshot()
})

it('additional', () => {
expect(generateTypeScript({
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string"
}
},
"required": ["firstName"],
"additionalProperties": {
"title": "Car",
"type": "object",
"properties": {
"newProp": {
"type": "Person"
}
},
"required": ["newProp"],
"additionalProperties": true
}
} as any)).toMatchSnapshot()
})
19 changes: 19 additions & 0 deletions __tests__/memo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { writeFileSync } from 'fs';

import schema from '../__fixtures__/schemas/memo.json';
import { generateTypeScript } from '../src';

it('memo', () => {
const code = generateTypeScript(schema as any);
expect(code).toMatchSnapshot();
writeFileSync(__dirname + '/../__fixtures__/output/memo.ts', code);
});

it('memo camelCase', () => {
const code = generateTypeScript(schema as any, {
useSingleQuotes: true,
useCamelCase: true
});
expect(code).toMatchSnapshot();
writeFileSync(__dirname + '/../__fixtures__/output/memo.camel.ts', code);
});
31 changes: 27 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type JSONSchemaProperty = {
items?: JSONSchemaProperty;
required?: string[];
$ref?: string;
additionalProperties?: boolean | JSONSchemaProperty;
};

interface SchemaTSOptions {
Expand Down Expand Up @@ -79,28 +80,47 @@ export function generateTypeScript(schema: JSONSchema, options?: SchemaTSOptions
throw e;
}
// Process the main schema
interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(schema.title), schema));
const title = schema.title;
if (!title) {
console.error('schema or options require a title');
}
interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(title), schema));
return generate(t.file(t.program(interfaces))).code;
}

function createInterfaceDeclaration(ctx: SchemaTSContext, name: string, schema: JSONSchema): t.TSInterfaceDeclaration {
const properties = schema.properties || {};
const required = schema.required || [];
const body = Object.keys(properties).map(key => {
const bodyElements = Object.keys(properties).map(key => {
const prop = properties[key];
return createPropertySignature(ctx, key, prop, required, schema);
});

// Handling additionalProperties
if (schema.additionalProperties) {
const additionalType = typeof schema.additionalProperties === 'boolean' ?
t.tsStringKeyword() : getTypeForProp(ctx, schema.additionalProperties, [], schema);

const indexSignature = t.tsIndexSignature(
[t.identifier("key")], // index name, can be any valid name
t.tsTypeAnnotation(additionalType)
);
indexSignature.parameters[0].typeAnnotation = t.tsTypeAnnotation(t.tsStringKeyword());
bodyElements.push(indexSignature);
}

const interfaceDeclaration = t.tsInterfaceDeclaration(
t.identifier(name),
null,
[],
t.tsInterfaceBody(body)
t.tsInterfaceBody(bodyElements)
);

// Make the interface exportable
return t.exportNamedDeclaration(interfaceDeclaration);
}


// Determine if the key is a valid JavaScript identifier
function isValidIdentifier(key) {
return /^[$A-Z_][0-9A-Z_$]*$/i.test(key) && !/^[0-9]+$/.test(key);
Expand Down Expand Up @@ -155,9 +175,12 @@ function getTypeForProp(ctx: SchemaTSContext, prop: JSONSchemaProperty, required
return createPropertySignature(ctx, nestedKey, nestedProp, nestedRequired, schema);
});
return t.tsTypeLiteral(typeElements);
// } else {
// throw new Error('Object must have properties');
} else {
throw new Error('Object must have properties');
return t.tsAnyKeyword();
}
break;
default:
return t.tsAnyKeyword();
}
Expand Down

0 comments on commit ea19b59

Please sign in to comment.