kiliman / remix-typedjson

This package is a replacement for superjson to use in your Remix app. It handles a subset of types that `superjson` supports, but is faster and smaller.
MIT License
435 stars 19 forks source link

Support Decimal type serialization #30

Closed brianbolnick closed 1 year ago

brianbolnick commented 1 year ago

For those of us using Prisma and the Decimal type, the current result of a serialized value is coming across as an object and not sticking with the true decimal.js type. 😄

brianbolnick commented 1 year ago

Opened #29 with similar implementation to the main superjson repo.

I realize Decimal.js is pretty large, happy to discuss alternatives for supporting this properly.

Thanks 😄

moishinetzer commented 1 year ago

Yes! Had this issue just now - have you figured out any other workaround in the meantime?

kiliman commented 1 year ago

Here's the preliminary API I added to support custom types. This way you can register custom types without bloating the existing package. Essentially "pay-for-use". You only pay the cost of the custom type if you use it.

API

type CustomTypeEntry<T> = {
  type: string
  is: (value: unknown) => boolean
  serialize: (value: T) => string
  deserialize: (value: string) => T
}

export function registerCustomType<T>(entry: CustomTypeEntry<T>) {
  customTypeMap.set(entry.type, entry)
}

Usage

Register the custom type in root.tsx once.

// root.tsx
import {
  typedjson,
  registerCustomType,
  useTypedLoaderData,
} from "remix-typedjson";

import Decimal from "decimal.js";

registerCustomType({
  type: "decimal",
  is: (value: unknown) => value instanceof Decimal,
  serialize: (value: Decimal) => value.toString(),
  deserialize: (value: string) => new Decimal(value),
});
// route.tsx
export function loader() {
  const d = new Decimal("1234567890123456789012345678901234567890");
  return typedjson({ greeting: "Hello World", today: new Date(), d });
}

export default function Index() {
  const loaderData = useTypedLoaderData<typeof loader>();

  return (
    <>
      <h2>Loader Data</h2>
      <pre>{JSON.stringify(loaderData, null, 2)}</pre>
      <ul>
        <li>today: {loaderData.today.toLocaleString()}</li>
        <li>d instanceof Decimal: {loaderData.d instanceof Decimal ? "true" : "false"}</li>
        <li>d: {loaderData.d.toFixed(0)}</li>
      </ul>
    </>
  )
}
image

image

kiliman commented 1 year ago

This has been published as v0.2.0

brianbolnick commented 10 months ago

Thanks @kiliman! I've found this to work a bit more flawlessly for those looking for Decimal support (specifically prisma decimal):

registerCustomType({
  type: "decimal",
  is: (value: unknown) => Prisma.Decimal.isDecimal(value),
  serialize: (value: Prisma.Decimal) => value.toJSON(),
  deserialize: (value: string) => new Prisma.Decimal(value),
});
kiliman commented 10 months ago

Yeah, this is flexible enough to support any type that the user needs, without bloating the main package.