Repository: fabien0102/ts-to-zod (ts)

Issue:
Cannot Generate Zod for type containing numeric keys
## Bug description
<!-- A clear and concise description of what the bug is. -->
It appears that ts-to-zod cannot generate the zod object when the type/interface contains an object with a numeric value for its key. Sample is from an OpenAPI typescript generated with [openapi-typescript](https://www.npmjs.com/package/openapi-typescript), but could be any interface with numeric keys.

### Input

```ts
export interface paths {
  "/job/cancel": {
    /**
     * Cancel Jobs 
     * @description Cancel one or more jobs, preventing their future execution
     */
    post: {
      /**
       * Cancel Jobs 
       * @description Cancel one or more jobs, preventing their future execution
       */
      requestBody?: {
        content: {
          "application/json": {
            id: string;
          } | ({
              id: string;
            })[];
        };
      };
      responses: {
        /** @description A successful response returns the id(s) of all job(s) that were cancelled. */
        200: {
          content: {
            "application/json": {
              id: string;
            } | ({
                id: string;
              })[];
          };
        };
      };
    };
  };
};
```

### Expected output

```ts
// value of responses
{
  // ...
  responses: z.object({
    200: z.object(/*...*/)
  })
}
```

### Actual output

```ts
// value of responses
{
  // ,,,
   responses: z.object({})
}
```

```txt
✖ Validating generated types
 ›   Error: 'pathsSchema' is not compatible with 'paths':
 ›   Argument of type '{ "/job/cancel": { post: { requestBody?: { content: { "application/json": { id: string; } | { id: string;
 ›    }[]; }; } | undefined; responses: {}; }; }; "/job/create": { post: { requestBody?: { content: { "application/json": { ...;
 ›    } | { ...; }[]; }; } | undefined; responses: {}; }; }; "/job/status": { ...; }; "/whoami...' is not assignable to
 ›   parameter of type 'paths'.
 ›     The types of '"/job/cancel".post.responses' are incompatible between these types.
 ›       Property '200' is missing in type '{}' but required in type '{ 200: { content: { "application/json": { id: string; } |
 ›   { id: string; }[]; }; }; }'.
```

**Versions**

- Typescript: `v4.8.3`
- Zod: `v3.19.1`


Pull request description:
fix: support numeric literal keys
# Why

Fix numeric literal object keys

Closed #117

Patch:
diff --git a/src/core/generate.ts b/src/core/generate.ts
index 4ac54ff..334a6b3 100644
--- a/src/core/generate.ts
+++ b/src/core/generate.ts
@@ -198,15 +198,13 @@ export function generate({
         !statements.has(varName) &&
         !zodSchemasWithMissingDependencies.has(varName)
     )
-    .forEach(
-      ({ varName, dependencies, statement, typeName, requiresImport }) => {
-        typeImports.add(typeName);
-        statements.set(varName, {
-          value: transformRecursiveSchema("z", statement, typeName),
-          typeName,
-        });
-      }
-    );
+    .forEach(({ varName, statement, typeName }) => {
+      typeImports.add(typeName);
+      statements.set(varName, {
+        value: transformRecursiveSchema("z", statement, typeName),
+        typeName,
+      });
+    });
 
   // Warn the user of possible not resolvable loops
   const errors: string[] = [];
diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts
index cf58008..55f971a 100644
--- a/src/core/generateZodSchema.ts
+++ b/src/core/generateZodSchema.ts
@@ -169,14 +169,18 @@ function buildZodProperties({
   skipParseJSDoc: boolean;
 }) {
   const properties = new Map<
-    ts.Identifier | ts.StringLiteral,
+    ts.Identifier | ts.StringLiteral | ts.NumericLiteral,
     ts.CallExpression | ts.Identifier | ts.PropertyAccessExpression
   >();
   members.forEach((member) => {
     if (
       !ts.isPropertySignature(member) ||
       !member.type ||
-      !(ts.isIdentifier(member.name) || ts.isStringLiteral(member.name))
+      !(
+        ts.isIdentifier(member.name) ||
+        ts.isStringLiteral(member.name) ||
+        ts.isNumericLiteral(member.name)
+      )
     ) {
       return;
     }
@@ -894,7 +898,7 @@ function buildZodObject({
           getDependencyName,
           skipParseJSDoc,
         })
-      : undefined;
+      : new Map();
 
   if (schemaExtensionClauses && schemaExtensionClauses.length > 0) {
     objectSchema = buildZodExtendedSchema(
@@ -902,7 +906,7 @@ function buildZodObject({
       properties.length > 0
         ? [
             f.createObjectLiteralExpression(
-              Array.from(parsedProperties!.entries()).map(([key, tsCall]) => {
+              Array.from(parsedProperties.entries()).map(([key, tsCall]) => {
                 return f.createPropertyAssignment(key, tsCall);
               }),
               true
@@ -913,7 +917,7 @@ function buildZodObject({
   } else if (properties.length > 0) {
     objectSchema = buildZodSchema(z, "object", [
       f.createObjectLiteralExpression(
-        Array.from(parsedProperties!.entries()).map(([key, tsCall]) => {
+        Array.from(parsedProperties.entries()).map(([key, tsCall]) => {
           return f.createPropertyAssignment(key, tsCall);
         }),
         true


Test patch:
diff --git a/src/core/generateZodSchema.test.ts b/src/core/generateZodSchema.test.ts
index ee6331c..9e4ee34 100644
--- a/src/core/generateZodSchema.test.ts
+++ b/src/core/generateZodSchema.test.ts
@@ -177,6 +177,30 @@ describe("generateZodSchema", () => {
     `);
   });
 
+  it("should generate a numerical key", () => {
+    const source = `export type responses = {
+     200: {
+      content: {
+        "application/json": {
+          id: string
+        }
+      }
+     }
+   };`;
+
+    expect(generate(source)).toMatchInlineSnapshot(`
+      "export const responsesSchema = z.object({
+          200: z.object({
+              content: z.object({
+                  \\"application/json\\": z.object({
+                      id: z.string()
+                  })
+              })
+          })
+      });"
+    `);
+  });
+
   it("should generate a promise schema", () => {
     const source = `export type KrytonResponse = Promise<boolean>`;
     expect(generate(source)).toMatchInlineSnapshot(