Haskell already has world-class support for converting data to and from JSON using the Aeson library. But what if you want to ensure that your TypeScript frontend is using your data types correctly?
If you already use Aeson’s autogenerated Template Haskell instances, then this library will let you generate TypeScript definitions that match up with them perfectly.
See the Github readme or the Hackage docs for more details, including some suggestions on how you can generate type files automatically as part of your frontend build process.
The library is on Stackage as of LTS-10.8.
Notes
This was my first significant Template Haskell project. Here are some notes on the implementation:
The key typeclass is called
TypeScript a
and the key functions within it aregetTypeScriptDeclarations :: Proxy a -> [TSDeclaration]
andgetTypeScriptType :: Proxy a -> String
. It turns outData.Proxy
is a great way to write functions that depend only on the type of something, rather than on a concrete value. As an extra bonus,Data.Proxy
is poly-kinded, so you can naturally use higher-kinded types with it. I initially wrote the library usingData.Tagged
butit wasn’t poly-kinded(EDIT: it is actually poly-kinded, but I couldn’t figure it out at the time) and was also slightly more verbose.Writing Template Haskell that works across all the different GHC versions can be painful. I found the th-abstraction library to be an essential resource for reifying types. Nonetheless, I wasn’t able to avoid a few
#if
macros to deal with slight changes in thetemplate-haskell
library over time.The way this library is tested is cool: with the exception of a few hand-coded spot checks, the tests just emit TypeScript definitions and values into a
.ts
file and then run the TypeScript compiler on it to make sure the compiler is happy with it.The hardest part of making this library match Aeson was all the different combinations of encoding options that Aeson supports. Aeson has 4 different “sum encoding” strategies for how to encode a value constructor, plus several additional boolean flags like
tagSingleConstructors
,unwrapUnaryRecords
, etc. There’s a slight TODO here; see this issue.