Skip to content

codedownio/aeson-typescript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

3d8bae3 · Feb 7, 2025
Feb 7, 2025
Dec 6, 2024
Dec 7, 2024
Aug 13, 2021
Dec 7, 2024
Feb 4, 2025
Jan 14, 2021
Jun 9, 2019
Feb 7, 2025
Mar 18, 2022
Oct 13, 2023
Dec 27, 2017
Nov 1, 2024
Feb 7, 2025
Feb 7, 2025
Nov 1, 2024
Jun 28, 2023
Jun 28, 2023
Jun 28, 2023
Jun 28, 2023
Feb 7, 2025
Feb 7, 2025
Jun 28, 2023
Jun 28, 2023
Dec 7, 2024
Dec 7, 2024
Jan 26, 2024
Dec 7, 2024
Feb 7, 2025
Feb 4, 2025
Feb 4, 2025
Feb 4, 2025
Feb 4, 2025
Jan 14, 2021

Repository files navigation

Welcome to aeson-typescript Hackage aeson-typescript

This library provides a way to generate TypeScript .d.ts files that match your existing Aeson ToJSON instances. If you already use Aeson's Template Haskell support to derive your instances, then deriving TypeScript is as simple as

$(deriveTypeScript myAesonOptions ''MyType)

For example,

data D a = Nullary
         | Unary Int
         | Product String Char a
         | Record { testOne   :: Double
                  , testTwo   :: Bool
                  -- | This docstring will go into the generated TypeScript!
                  , testThree :: D a
                  } deriving Eq

Next we derive the necessary instances.

$(deriveTypeScript (defaultOptions {fieldLabelModifier = drop 4, constructorTagModifier = map toLower}) ''D)

Now we can use the newly created instances.

>>> putStrLn $ formatTSDeclarations $ getTypeScriptDeclarations (Proxy :: Proxy (D T))

type D<T> = "nullary" | IUnary<T> | IProduct<T> | IRecord<T>;

type IUnary<T> = number;

type IProduct<T> = [string, string, T];

interface IRecord<T> {
  tag: "record";
  One: number;
  Two: boolean;
  // This docstring will go into the generated TypeScript!
  Three: D<T>;
}

It's important to make sure your JSON and TypeScript are being derived with the same options. For this reason, we include the convenience HasJSONOptions typeclass, which lets you write the options only once, like this:

instance HasJSONOptions MyType where getJSONOptions _ = (defaultOptions {fieldLabelModifier = drop 4})

$(deriveJSON (getJSONOptions (Proxy :: Proxy MyType)) ''MyType)
$(deriveTypeScript (getJSONOptions (Proxy :: Proxy MyType)) ''MyType)

Or, if you want to be even more concise and don't mind defining the instances in the same file,

myOptions = defaultOptions {fieldLabelModifier = drop 4}

$(deriveJSONAndTypeScript myOptions ''MyType)

Remembering that the Template Haskell Q monad is an ordinary monad, you can derive instances for several types at once like this:

$(mconcat <$> traverse (deriveJSONAndTypeScript myOptions) [''MyType1, ''MyType2, ''MyType3])

Suggestions for use

This library was written to make it easier to typecheck your TypeScript frontend against your Haskell backend. Here's how I like to integrate it into my workflow:

The idea is to set up a separate Haskell executable in your Cabal file whose sole purpose is to generate types. For example, in your hpack package.yaml file add a new executable like this:

executables:
  ...
  tsdef:
    main: Main.hs
    source-dirs: tsdef
    dependencies:
    - my-main-app
    ...

And tsdef/Main.hs should look like this:

module Main where

import Data.Proxy
import Data.Monoid
import MyLibraries

$(deriveTypeScript (getJSONOptions (Proxy :: Proxy MyType1)) ''MyType1)
$(deriveTypeScript (getJSONOptions (Proxy :: Proxy MyType2)) ''MyType2)
...

main = putStrLn $ formatTSDeclarations (
  (getTypeScriptDeclaration (Proxy :: Proxy MyType1)) <>
  (getTypeScriptDeclaration (Proxy :: Proxy MyType2)) <>
  ...
)

Now you can generate the types by running stack runhaskell tsdef/Main.hs > types.d.ts. I like to make this an automatic step in my Gulpfile, Webpack config, etc.

See also

If you want a more opinionated web framework for generating APIs, check out servant. If you use Servant, you may enjoy servant-typescript, which is based on aeson-typescript! This companion package also has the advantage of magically collecting all the types used in your API, so you don't have to list them out manually.

For another very powerful framework that can generate TypeScript client code based on an API specification, see Swagger/OpenAPI.