Skip to content
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

Extract interface metadata for runtime #58434

Closed
6 tasks done
NinjaKinshasa opened this issue May 4, 2024 · 2 comments
Closed
6 tasks done

Extract interface metadata for runtime #58434

NinjaKinshasa opened this issue May 4, 2024 · 2 comments

Comments

@NinjaKinshasa
Copy link

NinjaKinshasa commented May 4, 2024

πŸ” Search Terms

TypeScript
Runtime type checking
Interface validation
Type guards
Zod
Yup
Developer experience
TypeScript enhancements
Type safety
Code redundancy
Automatic type guards

βœ… Viability Checklist

⭐ Suggestion

I propose adding a feature to TypeScript that allows developers to extract interface descriptions at runtime for use in runtime type checking. This feature aims to reduce redundancy and streamline the process of ensuring runtime data matches predefined interfaces.

Something like:

const interfaceDescription = Describe<User>(); // Extract interface description...
/*
 { 
  User: {
    type: 'interface',
    properties: {
      id: { type: 'number' },
      username: { type: 'string' },
      email: { type: 'string' },
      age: { type: 'number' }
    }
  }
}; 
*/
// ...then use this object to check if some data correctly matches with the interface.

To be clear, what I suggest is not having dynamic types; it is simply adding a kind of "sugar" to TypeScript to make interface information available at runtime, without having to duplicate the code.

πŸ“ƒ Motivating Example

When programming with TypeScript, a common task is to ensure that some unknown data obtained at runtime matches an interface. Currently, the go-to methods involve using TypeScript type guards or external libraries like Zod or yup. However, these solutions all share the same problem: you must duplicate your interface behavior in a system that allows performing the check at runtime.

// interface
interface User {
  id: number;
  username: string;
  email: string;
  age: number;
}

// Type guard function
function isUser(obj: any): obj is User {
  return (
    typeof obj === "object" &&
    typeof obj.id === "number" &&
    typeof obj.username === "string" &&
    typeof obj.email === "string" &&
    typeof obj.age === "number"
  );
}

// Zod schema
import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  username: z.string(),
  email: z.string().email(),
  age: z.number(),
});

As a developer, I do not like this redundancy. What I would prefer is a way to have a generic type guard based on the interface.
With this, feature, you could simply write this :

function genericTypeGuard<T>(data: unknown, description: Record<string, any>): data is T {
   // for each attribute of description
   //     check that data[attribute] is of correct type
   //     implement it recursivity if you want
}

function OrderTypeGuard(data: unknown): obj is Order {
   const interfaceDescription = Describe<Order>();
   return genericTypeGuard<Order>(data, interfaceDescription);
}
function UserTypeGuard(data: unknown): obj is User {
   const interfaceDescription = Describe<User>();
   return genericTypeGuard<User>(data, interfaceDescription);
}

It is not totally generic, you still have to write one typeguard function by interface you want to validate, but it is way less painful than having to copy-paste all the interface into the guard.

πŸ’» Use Cases

  1. What do you want to use this for?

    Data Validation: Runtime type checking ensures that data received from external sources, like API responses or database queries, adheres to predefined interfaces without duplicating definitions.

    Improved Developer Experience: Eliminate the need for duplicating interface definitions, leading to cleaner, more maintainable code and reduced boilerplate.

    Reduced Maintenance Overhead: Interface descriptions available at runtime allow for changes to interface definitions without updating corresponding type guards or validation logic, reducing maintenance overhead and minimizing inconsistencies.

  2. What shortcomings exist with current approaches?

    Duplicated code: Current approaches involve duplicating interface definitions, leading to redundancy.
    Mistakes: Custom type guards may become outdated if not updated alongside interface changes.
    Maintenance: While libraries like Zod allow type inference, it's not possible to infer schemas from TypeScript interfaces, leading to maintenance challenges.

  3. What workarounds are you using in the meantime?

I define schemas using Zod with the "satisfies" keyword to ensure alignment with TypeScript interfaces.

@MartinJohns
Copy link
Contributor

MartinJohns commented May 4, 2024

This is explicitly out of scope for TypeScript. Check the "Viability Checklist" again, specifically "emitting based on the type of expressions", "this isn't a runtime feature" and the Design Goals, goal 9 and non-goal 5. It's also been suggested and declined numerous times already.

While libraries like Zod allow type inference, it's not possible to infer TypeScript interfaces from schemas,

That doesn't seem to be accurate. You should be able to do this: type User = z.infer<typeof UserSchema>;

@NinjaKinshasa
Copy link
Author

This is explicitly out of scope for TypeScript. Check the "Viability Checklist" again, specifically "emitting based on the type of expressions", "this isn't a runtime feature" and the Design Goals, goal 9 and non-goal 5. It's also been suggested and declined numerous times already.

Ok I didn't know about the design goals page , thank you for pointing it out. This feature would not keep typescript "fully erasable". I close this issue.

While libraries like Zod allow type inference, it's not possible to infer TypeScript interfaces from schemas,

That doesn't seem to be accurate. You should be able to do this: type User = z.infer;

You are correct I switched the words, I edited my first message to fix it.

@NinjaKinshasa NinjaKinshasa closed this as not planned Won't fix, can't repro, duplicate, stale May 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants