flightcontrolhq / superjson

Safely serialize JavaScript expressions to a superset of JSON, which includes Dates, BigInts, and more.
https://www.flightcontrol.dev?ref=superjson
MIT License
4.13k stars 91 forks source link

More accurate types #209

Closed Jacob-Lockwood closed 1 year ago

Jacob-Lockwood commented 1 year ago

Hello! I'm making a library with superjson as a main element of it, but I'm running into issues with typings. I want my lib to be 100% typesafe but I'm having to throw around lots of as assertions and anys to achieve that. The main issues I've run into are with the SuperJSONResult type. It's not generic, so it's hard to deal with:

export interface SuperJSONResult {
  json: JSONValue;
  meta?: {
    values?: MinimisedTree<TypeAnnotation>;
    referentialEqualities?: ReferentialEqualityAnnotations;
  };
}
interface SomeJSON {
  name: string;
  createdAt: Date;
  users: Map<string, string>;
}
// I want this, but it's not currently possible:
type SerializedResult = SuperJSONResult<SomeJSON> 
/* type SerializedResult = {
  json: {
    name: string;
    createdAt: string;
    users: [string, string][];
  };
  meta: {
    values: {
      createdAt: ["Date"];
      users: ["map"];
    };
  };
}; */

Ideally, the types for this (and other places) would be fully generic and accurate. I realize this would be a big undertaking and might be considered out-of-scope, but I'd be happy to try to implement this. I think this would also have to be a major version upgrade since this is a breaking change.

Jacob-Lockwood commented 1 year ago

Here's my first stab at the problem: TS Playground link. I think this is close to correctness, but I'm not familiar enough with the ins and outs of SuperJSON to know 100%.

I thought about it a little more and I don't think this really has to be in SuperJSON natively. If I don't get a response within the next few days I think I'll close this and just use what I have now for my library.

Skn0tt commented 1 year ago

Hey @jacobofbrooklyn! This is a lovely idea, and the TS playground you sent is a really nice use of typescript features to make this work :)

I agree that having this in SuperJSON natively is probably not the best idea. I see the contents of meta as being implementation details. Giving outside outside documentation about the exact structure of it, as we'd do with what you're proposing, would constrain us to a) preserve compatibility to that type b) limit us to only use JS structures that can be type-constructed (like in your playground). If I'd change anything, I'd probably change the type of meta that we return publically to unknown, to convey exactly this 😅

I'm curious, what's the library you're building? Would love to take a look, and maybe we could put a link to it somewhere in our README :)

Jacob-Lockwood commented 1 year ago

I'm making a new version of the package superjson-remix which isn't actively maintained anymore and has a couple big flaws, and I'm changing a few things about the API. Basically Remix has really accurate types for how it serializes JSON, and I wanted types for deserialization to be just as accurate. However, the deserialization is obviously much more complicated, and I'm not sure I'm really going to be able to do this how I wanted to since I haven't been able to get deserialization working in the type-system.

It's not a very big package anyways so I don't think it's really worth all this lol. I'm kind of just having fun with TS to be honest. If I am able to get deserialization working, I think I'll publish these types as a standalone NPM package.

Jacob-Lockwood commented 1 year ago

I'm going to close this issue now since it's not really an issue with superjson.

Jacob-Lockwood commented 1 year ago

I forgot about this and just came back to it. Didn't take long for me to figure out some form of deserialization, but it's not perfect (doesn't really support nesting yet) and I'm not sure how accurate it is to the original package. I'll test it a little and put together an NPM package for it.