aeson-typescript
Generate TypeScript definition files from your ADTs
https://github.com/codedownio/aeson-typescript#readme
LTS Haskell 22.39: | 0.6.3.0 |
Stackage Nightly 2024-10-29: | 0.6.3.0 |
Latest on Hackage: | 0.6.3.0 |
aeson-typescript-0.6.3.0@sha256:d830b0eab4f9d2b3735480a28e2d842fc40e67b52088ef262c34c163368235ff,3995
Module documentation for 0.6.3.0
- Data
- Data.Aeson
Welcome to 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.
Changes
Change log
Unreleased
0.6.3.0
- GHC 9.8 support
0.6.2.0
- Expose generic type constructors
T4
throughT10
. (We only exposedT
,T1
,T2
, andT3
before.)
0.6.1.0
- Fix a bug which caused enum formatting mode to turn off when multiple declarations were provided (#41)
- Fix some mismatch issues where an enum value doesn’t match the desired string.
0.6.0.0
- New word instances: Word, Word16, Word32, Word64
- New instances from Data.Functor: Compose, Const, Identity, Product
0.5.0.0
- #35
- Add
Data.Aeson.TypeScript.LegalName
module for checking whether a name is a legal JavaScript name or not. - The
defaultFormatter
willerror
if the name contains illegal characters.
- Add
- Be able to transfer Haddock comments to emitted TypeScript (requires GHC >= 9.2 and
-haddock
flag) - Add support for @no-emit-typescript in Haddocks for constructors and record fields (requires GHC >= 9.2 and
-haddock
flag) - Support GHC 9.6.1
0.4.2.0
- Fix TypeScript (A.KeyMap a) instance
0.4.1.0
- Add TypeScript Int16
- Add TypeScript (A.KeyMap a) instance for aeson 2
0.4.0.0
- Add new built-in instances (Word8, Int32, Int64, Map, HashSet)
- Export TSField in the Internal module
- Avoid producing redundant constraints (for fewer warnings when using -Wredundant-constraints)
- Encode maps as mapped types (allows you to have unions as keys)
- Support mapping open type families to lookup types (+ progress on handling promoted types)
- Improve propagation of T variables in declarations
- Add support for “key types”, in case you have custom implementations of FromJSONKey/ToJSONKey
- Add ability to recursively derive missing instances (fragile)
0.3.0.1
- Support GHC 9.0.1
0.3.0.0
- Update th-abstraction dependency to < 0.5 to support working with newer Stack LTS.
- Major refactors to improve TH quality.
- Tracking of parent types to allow recursive deriving
- The
getParentTypes
function was added to the main typeclass. - The new
Data.Aeson.TypeScript.Recursive
module for working with recursive definitions.
- The
- New support for mapping Haskell closed type families to TypeScript lookup types.
0.2.0.0
- New formatting option
interfaceNameModifier
.
0.1.0.0
- Initial release.