diff --git a/Data/Iso.hs b/Data/Iso.hs
deleted file mode 100644
--- a/Data/Iso.hs
+++ /dev/null
@@ -1,14 +0,0 @@
--- | Convenience module that re-exports the available submodules.
-module Data.Iso (
-
-  module Data.Iso.Core,
-  module Data.Iso.Common,
-  module Data.Iso.TH,
-  module Data.Semigroup
-
-  ) where
-
-import Data.Iso.Core
-import Data.Iso.Common
-import Data.Iso.TH
-import Data.Semigroup
diff --git a/Data/Iso/Common.hs b/Data/Iso/Common.hs
deleted file mode 100644
--- a/Data/Iso/Common.hs
+++ /dev/null
@@ -1,94 +0,0 @@
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE NoMonoPatBinds #-}
-
--- | Constructor-destructor isomorphisms for some common datatypes.
-module Data.Iso.Common (
-
-  -- * @()@
-  unit,
-  
-  -- * @(,)@
-  tup,
-  
-  -- * @(,,)@
-  tup3,
-
-  -- * @Maybe a@
-  nothing, just, maybe,
-  
-  -- * @[a]@
-  nil, cons,
-  
-  -- * @Either a b@
-  left, right, either,
-  
-  -- * @Bool@
-  false, true, bool
-
-  ) where
-
-import Prelude hiding (id, (.), maybe, either)
-import Control.Category
-
-import Data.Iso.Core
-import Data.Iso.TH
-
-import Data.Semigroup
-
-
-unit :: Iso t (() :- t)
-unit = Iso f g
-  where
-    f       t  = Just (() :- t)
-    g (_ :- t) = Just t
-
-tup :: Iso (a :- b :- t) ((a, b) :- t)
-tup = Iso f g
-  where
-    f (a :- b :- t) = Just ((a, b) :- t)
-    g ((a, b) :- t) = Just (a :- b :- t)
-
-tup3 :: Iso (a :- b :- c :- t) ((a, b, c) :- t)
-tup3 = Iso f g
-  where
-    f (a :- b :- c :- t) = Just ((a, b, c) :- t)
-    g ((a, b, c) :- t) = Just (a :- b :- c :- t)
-
-nothing :: Iso t (Maybe a :- t)
-just    :: Iso (a :- t) (Maybe a :- t)
-(nothing, just) = $(deriveIsos ''Maybe)
-
-maybe :: Iso t (a :- t) -> Iso t (Maybe a :- t)
-maybe el = just . el <> nothing
-
-
-nil :: Iso t ([a] :- t)
-nil = Iso f g
-  where
-    f        t  = Just ([] :- t)
-    g ([] :- t) = Just t
-    g _         = Nothing
-
-cons :: Iso (a :- [a] :- t) ([a] :- t)
-cons = Iso f g
-  where
-    f (x :- xs  :- t) = Just ((x : xs) :- t)
-    g ((x : xs) :- t) = Just (x :- xs :- t)
-    g _               = Nothing
-
-
-left  :: Iso (a :- t) (Either a b :- t)
-right :: Iso (b :- t) (Either a b :- t)
-(left, right) = $(deriveIsos ''Either)
-
-either :: Iso t1 (a :- t2) -> Iso t1 (b :- t2) -> Iso t1 (Either a b :- t2)
-either f g = left . f <> right . g
-
-
-false :: Iso t (Bool :- t)
-true  :: Iso t (Bool :- t)
-(false, true) = $(deriveIsos ''Bool)
-
-bool  :: Iso t (Bool :- t)
-bool = false <> true
diff --git a/Data/Iso/Core.hs b/Data/Iso/Core.hs
deleted file mode 100644
--- a/Data/Iso/Core.hs
+++ /dev/null
@@ -1,120 +0,0 @@
-{-# LANGUAGE TypeOperators #-}
-
-module Data.Iso.Core (
-
-  -- * Partial isomorphisms
-  Iso(..), convert, inverse, many,
-  
-  -- * Stack-based isomorphisms
-  (:-)(..), stack, unstack, swap, duck,
-  lit, inverseLit, matchWithDefault, ignoreWithDefault
-  
-  ) where
-
-
-import Prelude hiding (id, (.), head)
-
-import Data.Monoid
-import Data.Semigroup
-
-import Control.Applicative hiding (many)
-import Control.Monad
-import Control.Category
-
-
--- Partial isomorphisms
-
--- | Bidirectional partial isomorphism.
-data Iso a b = Iso (a -> Maybe b) (b -> Maybe a)
-
-instance Category Iso where
-  id                          = Iso Just Just
-  ~(Iso f1 g1) . ~(Iso f2 g2) = Iso (f1 <=< f2) (g1 >=> g2)
-
-instance Monoid (Iso a b) where
-  mempty = Iso (const Nothing) (const Nothing)
-  ~(Iso f1 g1) `mappend` ~(Iso f2 g2) =
-    Iso
-      ((<|>) <$> f1 <*> f2)
-      ((<|>) <$> g1 <*> g2)
-
-instance Semigroup (Iso a b) where
-  (<>) = mappend
-
--- | Apply an isomorphism in one direction.
-convert :: Iso a b -> a -> Maybe b
-convert (Iso f _) = f
-
--- | Inverse of an isomorphism.
-inverse :: Iso a b -> Iso b a
-inverse (Iso f g) = Iso g f
-
--- | Apply an isomorphism as many times as possible, greedily.
-many :: Iso a a -> Iso a a
-many (Iso f g) = Iso manyF manyG
-  where
-    manyF = ((<|>) <$> (f >=> manyF) <*> Just)
-    manyG = ((<|>) <$> (g >=> manyG) <*> Just)
-
-
--- Stack-based isomorphisms
-
--- | Heterogenous stack with a head and a tail.
-data h :- t = h :- t
-  deriving (Eq, Show)
-infixr 5 :-
-
-head :: (h :- t) -> h
-head (h :- _) = h
-
--- | Convert to a stack isomorphism.
-stack :: Iso a b -> Iso (a :- t) (b :- t)
-stack (Iso f g) = Iso (lift f) (lift g)
-  where
-    lift k (x :- t) = (:- t) <$> k x
-
--- | Convert from a stack isomorphism.
-unstack :: Iso (a :- ()) (b :- ()) -> Iso a b
-unstack (Iso f g) = Iso (lift f) (lift g)
-  where
-    lift k = fmap head . k . (:- ())
-
--- | Swap the top two arguments.
-swap :: Iso (a :- b :- t) (b :- a :- t)
-swap = Iso f f
-  where
-    f (x :- y :- t) = Just (y :- x :- t)
-
--- | Introduce a head value that is passed unmodified.
-duck :: Iso t1 t2 -> Iso (h :- t1) (h :- t2)
-duck (Iso f g) = Iso (lift f) (lift g)
-  where
-    lift k (h :- t) = (h :-) <$> k t
-
--- | Push or pop a specific value.
-lit :: Eq a => a -> Iso t (a :- t)
-lit x = Iso f g
-  where
-    f t = Just (x :- t)
-    g (x' :- t) = do
-      guard (x' == x)
-      Just t
-
--- | Inverse of 'lit'.
-inverseLit :: Eq a => a -> Iso (a :- t) t
-inverseLit = inverse . lit
-
--- | When converting from left to right, push the default value on top of the
--- stack. When converting from right to left, pop the value, make sure it
--- matches the predicate and then discard it.
-matchWithDefault :: (a -> Bool) -> a -> Iso t (a :- t)
-matchWithDefault p x = Iso f g
-  where
-    f t = Just (x :- t)
-    g (x' :- t) = do
-      guard (p x')
-      return t
-
--- | When converting from left to right, push the default value on top of the stack. When converting from right to left, pop the value and discard it.
-ignoreWithDefault :: a -> Iso t (a :- t)
-ignoreWithDefault = matchWithDefault (const True)
diff --git a/Data/Iso/TH.hs b/Data/Iso/TH.hs
deleted file mode 100644
--- a/Data/Iso/TH.hs
+++ /dev/null
@@ -1,104 +0,0 @@
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE TemplateHaskell #-}
-
-module Data.Iso.TH (deriveIsos) where
-
-import Data.Iso.Core
-import Language.Haskell.TH
-import Control.Applicative
-import Control.Monad
-
-
--- | Derive partial isomorphisms for a given datatype. The resulting
--- expression is a tuple with one isomorphism element for each constructor in
--- the datatype.
--- 
--- For example:
--- 
--- > nothing :: Iso t (Maybe a :- t)
--- > just    :: Iso (a :- t) (Maybe a :- t)
--- > (nothing, just) = $(deriveIsos ''Maybe)
--- 
--- Deriving isomorphisms this way requires @-XNoMonoPatBinds@.
-deriveIsos :: Name -> Q Exp
-deriveIsos name = do
-  info <- reify name
-  routers <-
-    case info of
-      TyConI (DataD _ _ _ cons _)   ->
-        mapM (deriveIso (length cons /= 1)) cons
-      TyConI (NewtypeD _ _ _ con _) ->
-        (:[]) <$> deriveIso False con
-      _ ->
-        fail $ show name ++ " is not a datatype."
-  return (TupE routers)
-
-
-deriveIso :: Bool -> Con -> Q Exp
-deriveIso matchWildcard con =
-  case con of
-    NormalC name tys -> go name (map snd tys)
-    RecC name tys -> go name (map (\(_,_,ty) -> ty) tys)
-    _ -> fail $ "Unsupported constructor " ++ show (conName con)
-  where
-    go name tys = do
-      iso <- [| Iso |]
-      isoCon <- deriveConstructor name tys
-      isoDes <- deriveDestructor matchWildcard name tys
-      return $ iso `AppE` isoCon `AppE` isoDes
-
-
-deriveConstructor :: Name -> [Type] -> Q Exp
-deriveConstructor name tys = do
-  -- Introduce some names
-  t          <- newName "t"
-  fieldNames <- replicateM (length tys) (newName "a")
-
-  -- Figure out the names of some constructors
-  ConE just  <- [| Just |]
-  ConE cons  <- [| (:-) |]
-
-  let pat = foldr (\f fs -> ConP cons [VarP f, fs]) (VarP t) fieldNames
-  let applyCon = foldl (\f x -> f `AppE` VarE x) (ConE name) fieldNames
-  -- applyCon <- [| undefined |]
-  let body = ConE just `AppE` (ConE cons `AppE` applyCon `AppE` VarE t)
-
-  return $ LamE [pat] body
-
-
-deriveDestructor :: Bool -> Name -> [Type] -> Q Exp
-deriveDestructor matchWildcard name tys = do
-  -- Introduce some names
-  x          <- newName "x"
-  r          <- newName "r"
-  fieldNames <- replicateM (length tys) (newName "a")
-
-  -- Figure out the names of some constructors
-  ConE just  <- [| Just |]
-  ConE cons  <- [| (:-) |]
-  nothing    <- [| Nothing |]
-
-  let conPat   = ConP name (map VarP fieldNames)
-  let okBody   = ConE just `AppE`
-                  foldr
-                    (\h t -> ConE cons `AppE` VarE h `AppE` t)
-                    (VarE r)
-                    fieldNames
-  let okCase   = Match (ConP cons [conPat, VarP r]) (NormalB okBody) []
-  let failCase = Match WildP (NormalB nothing) []
-  let allCases =
-        if matchWildcard
-              then [okCase, failCase]
-              else [okCase]
-
-  return $ LamE [VarP x] (CaseE (VarE x) allCases)
-
-
--- Retrieve the name of a constructor.
-conName :: Con -> Name
-conName con =
-  case con of
-    NormalC name _  -> name
-    RecC name _     -> name
-    InfixC _ name _ -> name
-    ForallC _ _ con' -> conName con'
diff --git a/JsonGrammar.cabal b/JsonGrammar.cabal
--- a/JsonGrammar.cabal
+++ b/JsonGrammar.cabal
@@ -1,5 +1,5 @@
 Name:           JsonGrammar
-Version:        0.3.5
+Version:        1.0
 Synopsis:       Combinators for bidirectional JSON parsing
 Description:   	Combinators for bidirectional JSON parsing
 
@@ -7,9 +7,9 @@
 Author:         Martijn van Steenbergen
 Maintainer:     martijn@van.steenbergen.nl
 Stability:      Experimental
-Copyright:      Some Rights Reserved (CC) 2010-2012 Martijn van Steenbergen
-Homepage:       https://github.com/MedeaMelana/JsonGrammar
-Bug-reports:    https://github.com/MedeaMelana/JsonGrammar/issues
+Copyright:      Some Rights Reserved (CC) 2010-2014 Martijn van Steenbergen
+Homepage:       https://github.com/MedeaMelana/JsonGrammar2
+Bug-reports:    https://github.com/MedeaMelana/JsonGrammar2/issues
 
 
 Cabal-Version:  >= 1.8
@@ -19,15 +19,24 @@
 Build-type:     Simple
 
 
+Source-Repository head
+  Type:         git
+  Location:     https://github.com/MedeaMelana/JsonGrammar2
+
 Library
-  Exposed-Modules:  Data.Iso,
-                    Data.Iso.Core,
-                    Data.Iso.TH,
-                    Data.Iso.Common,
-                    Language.JsonGrammar
+  Hs-Source-Dirs:   src
+  Exposed-Modules:  Language.JsonGrammar
+  Other-Modules:    Language.JsonGrammar.Grammar,
+                    Language.JsonGrammar.Parser,
+                    Language.JsonGrammar.TypeScript,
+                    Language.JsonGrammar.Serializer,
+                    Language.JsonGrammar.Util
   Build-Depends:    base >= 3.0 && < 5,
                     aeson >= 0.6 && < 0.8,
-                    semigroups >= 0.5 && < 0.9,
+                    semigroups >= 0.5 && < 0.16,
+                    language-typescript >= 0.0.4 && < 0.1,
+                    mtl >= 2.1 && < 2.3,
+                    stack-prism < 0.2,
                     -- constraints copied from aeson-0.6.1.0:
                     attoparsec >= 0.8.6.1,
                     bytestring,
@@ -39,18 +48,17 @@
                     unordered-containers >= 0.1.3.0,
                     vector >= 0.7.1
 
-Source-Repository head
-  Type:         git
-  Location:     https://github.com/MedeaMelana/JsonGrammar
-
-
 Test-Suite tests
-  Type: exitcode-stdio-1.0
-  Hs-Source-Dirs: tests
-  Main-Is: Tests.hs
+  Type:             exitcode-stdio-1.0
+  Hs-Source-Dirs:   tests
+  Main-Is:          Tests.hs
+  Other-Modules:    Types
   Build-Depends:    JsonGrammar,
+                    stack-prism < 0.2,
                     base >= 3.0 && < 5,
                     aeson >= 0.6 && < 0.8,
-                    test-framework,
-                    test-framework-hunit,
-                    HUnit
+                    language-typescript >= 0.0.4 && < 0.1,
+                    text >= 0.11.0.2,
+                    test-framework >= 0.8 && < 0.9,
+                    test-framework-hunit >= 0.3 && < 0.4,
+                    HUnit >= 1.2 && < 1.3
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011, Martijn van Steenbergen
+Copyright (c) 2013, Martijn van Steenbergen
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/Language/JsonGrammar.hs b/Language/JsonGrammar.hs
deleted file mode 100644
--- a/Language/JsonGrammar.hs
+++ /dev/null
@@ -1,222 +0,0 @@
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverlappingInstances #-}
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE NoMonoPatBinds #-}
-
-module Language.JsonGrammar (
-  -- * Constructing JSON grammars
-  liftAeson, option, greedyOption, list, elementBy, array,
-  propBy, rawFixedProp, rest, ignoreRest, object,
-  
-  -- * Type-directed conversion
-  Json(..), fromJson, toJson, litJson, prop, fixedProp, element
-  
-  ) where
-
-import Prelude hiding (id, (.), head, maybe, either)
-
-import Data.Aeson hiding (object)
-import Data.Aeson.Types (parseMaybe)
-import Data.Attoparsec.Number
-import Data.Hashable (Hashable)
-import Data.Int
-import Data.IntSet (IntSet)
-import Data.Iso hiding (option)
-import qualified Data.HashMap.Lazy as M
-import Data.Maybe (fromMaybe, isNothing)
-import Data.String
-import Data.Text (Text)
-import qualified Data.Text.Lazy as Lazy
-import Data.Time.Clock
-import qualified Data.Vector as V
-import qualified Data.Vector.Generic as VG
-import qualified Data.Vector.Fusion.Stream as VS
-import Data.Word
-
-import Control.Category
-import Control.Monad
-
-
-aeObject :: Iso (Object :- t) (Value :- t)
-aeArray  :: Iso (Array  :- t) (Value :- t)
-aeNull   :: Iso            t  (Value :- t)
-(aeObject, aeArray, _, _, _, aeNull) = $(deriveIsos ''Value)
-
--- | Convert any Aeson-enabled type to a grammar.
-liftAeson :: (FromJSON a, ToJSON a) => Iso (Value :- t) (a :- t)
-liftAeson = stack (Iso from to)
-  where
-    from = parseMaybe parseJSON
-    to   = Just . toJSON
-
--- | Introduce 'Null' as possible value. First gives the argument grammar a
--- chance, only yielding 'Null' or 'Nothing' if the argument grammar fails to
--- handle the input.
-option :: Iso (Value :- t) (a :- t) -> Iso (Value :- t) (Maybe a :- t)
-option g = just . g <> nothing . inverse aeNull
-
--- | Introduce 'Null' as possible (greedy) value. Always converts 'Nothing' to
--- 'Null' and vice versa, even if the argument grammar knows how to handle
--- these values.
-greedyOption :: Iso (Value :- t) (a :- t) -> Iso (Value :- t) (Maybe a :- t)
-greedyOption g = nothing . inverse aeNull <> just . g
-
--- | Convert between a JSON array and Haskell list of arbitrary lengts. The
--- elements are converted using the argument grammar.
-list :: Iso (Value :- t) (a :- t) -> Iso (Value :- t) ([a] :- t)
-list g = duck nil >>> array (many single)
-  where
-    -- With ScopedTypeVariables:
-    -- single :: Iso ([Value] :- [a] :- t) ([Value] :- [a] :- t)
-    single = swap                -- [a] :- [Value] :- t
-         >>> duck (elementBy g)  -- [a] :- [Value] :- a :- t
-         >>> swap                -- [Value] :- [a] :- a :- t
-         >>> duck swap           -- [Value] :- a :- [a] :- t
-         >>> duck cons           -- [Value] :- [a] :- t
-
--- | Wrap a bunch of elements in a JSON array. For example, to match an array of exactly length two:
---
--- > array (element . element)
---
--- Or to match an empty array:
---
--- > array id
-array :: Iso ([Value] :- t1) ([Value] :- t2) -> Iso (Value :- t1) t2
-array els = inverse aeArray    -- Vector Value :- t1
-        >>> vectorReverseList  -- [Value] :- t1
-        >>> els                -- [Value] :- t2
-        >>> inverse nil        -- t2
-
--- | Describe a single array element with the given grammar.
-elementBy :: Iso (Value :- t1) t2 -> Iso ([Value] :- t1) ([Value] :- t2)
-elementBy g = inverse cons  -- Value   :- [Value] :- t
-          >>> swap          -- [Value] :- Value :- t
-          >>> duck g        -- [Value] :- a :- t
-
-vectorReverseList :: Iso (V.Vector a :- t) ([a] :- t)
-vectorReverseList = stack (Iso f g)
-  where
-    f = Just . VS.toList    . VG.streamR
-    g = Just . VG.unstreamR . VS.fromList
-
-
--- | Describe a property with the given name and value grammar.
-propBy :: Iso (Value :- t) (a :- t) -> String -> Iso (Object :- t) (Object :- a :- t)
-propBy g name = duck g . rawProp name
-
-rawProp :: String -> Iso (Object :- t) (Object :- Value :- t)
-rawProp name = Iso from to
-  where
-    textName = fromString name
-    from (o :- r) = do
-      value <- M.lookup textName o
-      return (M.delete textName o :- value :- r)
-    to (o :- value :- r) = do
-      guard (notMember textName o)
-      return (M.insert textName value o :- r)
-
--- | Expect a specific key/value pair.
-rawFixedProp :: String -> Value -> Iso (Object :- t) (Object :- t)
-rawFixedProp name value = stack (Iso from to)
-  where
-    textName = fromString name
-    from o = do
-      value' <- M.lookup textName o
-      guard (value' == value)
-      return (M.delete textName o)
-    to o = do
-      guard (notMember textName o)
-      return (M.insert textName value o)
-
--- Defined in Data.Map but not in Data.HashMap.Lazy:
-notMember :: (Eq k, Hashable k) => k -> M.HashMap k v -> Bool
-notMember k m = isNothing (M.lookup k m)
-
--- | Collect all properties left in an object.
-rest :: Iso (Object :- t) (Object :- M.HashMap Text Value :- t)
-rest = lit M.empty
-
--- | Match and discard all properties left in the object. When converting back to JSON, produces no properties.
-ignoreRest :: Iso (Object :- t) (Object :- t)
-ignoreRest = lit M.empty . inverse (ignoreWithDefault M.empty)
-
--- | Wrap an exhaustive bunch of properties in an object. Typical usage:
--- 
--- > object (prop "key1" . prop "key2")
-object :: Iso (Object :- t1) (Object :- t2) -> Iso (Value :- t1) t2
-object props = inverse aeObject >>> props >>> inverseLit M.empty
-
-
--- Type-directed conversion
-
--- | Convert values of a type to and from JSON.
-class Json a where
-  grammar :: Iso (Value :- t) (a :- t)
-
-instance Json a => Json [a] where
-  grammar = list grammar
-
-instance Json a => Json (Maybe a) where
-  grammar = option grammar
-
-instance (Json a, Json b) => Json (Either a b) where
-  grammar = either grammar grammar
-
-
-instance Json Bool            where grammar = liftAeson
-instance Json Char            where grammar = liftAeson
-instance Json Double          where grammar = liftAeson
-instance Json Float           where grammar = liftAeson
-instance Json Int             where grammar = liftAeson
-instance Json Int8            where grammar = liftAeson
-instance Json Int16           where grammar = liftAeson
-instance Json Int32           where grammar = liftAeson
-instance Json Int64           where grammar = liftAeson
-instance Json Integer         where grammar = liftAeson
-instance Json Word            where grammar = liftAeson
-instance Json Word8           where grammar = liftAeson
-instance Json Word16          where grammar = liftAeson
-instance Json Word32          where grammar = liftAeson
-instance Json Word64          where grammar = liftAeson
-instance Json ()              where grammar = liftAeson
-instance Json Number          where grammar = liftAeson
-instance Json Text            where grammar = liftAeson
-instance Json Lazy.Text       where grammar = liftAeson
-instance Json IntSet          where grammar = liftAeson
-instance Json UTCTime         where grammar = liftAeson
-instance Json DotNetTime      where grammar = liftAeson
-instance Json Value           where grammar = id
-instance Json [Char]          where grammar = liftAeson
-
-unsafeToJson :: Json a => String -> a -> Value
-unsafeToJson context value =
-    fromMaybe err (convert (inverse (unstack grammar)) value)
-  where
-    err = error (context ++
-            ": could not convert Haskell value to JSON value")
-
--- | Convert from JSON.
-fromJson :: Json a => Value -> Maybe a
-fromJson = convert (unstack grammar)
-
--- | Convert to JSON.
-toJson :: Json a => a -> Maybe Value
-toJson = convert (inverse (unstack grammar))
-
--- | Expect/produce a specific JSON 'Value'.
-litJson :: Json a => a -> Iso (Value :- t) t
-litJson = inverseLit . unsafeToJson "litJson"
-
--- | Describe a property whose value grammar is described by a 'Json' instance.
-prop :: Json a => String -> Iso (Object :- t) (Object :- a :- t)
-prop = propBy grammar
-
--- | Expect a specific key/value pair.
-fixedProp :: Json a => String -> a -> Iso (Object :- t) (Object :- t)
-fixedProp name value = rawFixedProp name (unsafeToJson "fixedProp" value)
-
--- | Describe a single array element whose grammar is given by a 'Json'
--- instance.
-element :: Json a => Iso ([Value] :- t) ([Value] :- a :- t)
-element = elementBy grammar
diff --git a/src/Language/JsonGrammar.hs b/src/Language/JsonGrammar.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/JsonGrammar.hs
@@ -0,0 +1,97 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeOperators #-}
+
+-- | JsonGrammar allows you to express a bidirectional mapping between Haskell datatypes and JSON ASTs in one go.
+module Language.JsonGrammar (
+
+    -- * The Aeson example
+    -- $example
+
+    -- * Types
+    Grammar, Context(..), (:-)(..),
+
+    -- * Elemental building blocks
+    pure, many, literal, label, object, property, array, element, coerce,
+
+    -- * Constructing grammars
+    fromPrism, defaultValue,
+
+    -- * Wrapping constructors
+    nil, cons, tup2,
+
+    -- * Type-directed grammars
+    Json(..), el, prop,
+
+    -- * Using grammars
+    parse, serialize, interfaces, SomeGrammar(..)
+
+  ) where
+
+import Prelude hiding ((.))
+import Control.Category ((.))
+import Data.Aeson.Types (Parser)
+
+import Language.JsonGrammar.Grammar
+import Language.JsonGrammar.Parser
+import Language.JsonGrammar.Serializer
+import Language.JsonGrammar.TypeScript
+
+-- $example
+--
+-- Aeson provides this example datatype:
+--
+-- > data Person = Person
+-- >     { name :: Text
+-- >     , age  :: Int
+-- >     } deriving Show
+--
+-- With these conversion functions:
+--
+-- > {-# LANGUAGE OverloadedStrings #-}
+-- >
+-- > instance FromJSON Person where
+-- >     parseJSON (Object v) = Person <$>
+-- >                            v .: "name" <*>
+-- >                            v .: "age"
+-- >     -- A non-Object value is of the wrong type, so fail.
+-- >     parseJSON _          = mzero
+-- >
+-- > instance ToJSON Person where
+-- >     toJSON (Person name age) = object ["name" .= name, "age" .= age]
+--
+-- From JsonGrammar's point of view, the problem with writing the conversions this way is that the same thing is written down twice: from one conversion, one can figure out what the conversion in the opposite direction should look like.
+--
+-- In JsonGrammar, the conversion looks like this:
+--
+-- > {-# LANGUAGE TemplateHaskell #-}
+-- >
+-- > deriveStackPrismsFor ["person"] ''Person
+-- >
+-- > instance Json Person where
+-- >   grammar = fromPrism person . object (prop "name" . prop "age")
+--
+-- This expresses the conversion in both directions in one go. The resulting parser and serializer are each other's inverse by construction.
+--
+-- As a bonus, if you name your grammar, JsonGrammar will generate a TypeScript definition for you:
+--
+-- > instance Json Person where
+-- >   grammar = label "Person" $
+-- >     fromPrism person . object (prop "name" . prop "age")
+--
+-- This results in this TypeScript definition:
+--
+-- > interface Person {age : number ;name : string ;}
+
+-- | Parse a JSON value according to the specified grammar.
+parse :: Grammar Val (a :- ()) (b :- ()) -> a -> Parser b
+parse = parseValue . unstack
+
+-- | Serialize a Haskell value to a JSON value according to the specified grammar.
+serialize :: Grammar Val (a :- ()) (b :- ()) -> b -> Maybe a
+serialize = serializeValue . unstack
+
+unstack :: Grammar c (a :- ()) (b :- ()) -> Grammar c a b
+unstack g = pure hd unhd . g . pure unhd hd
+  where
+    hd (x :- ()) = return x
+    unhd x       = return (x :- ())
diff --git a/src/Language/JsonGrammar/Grammar.hs b/src/Language/JsonGrammar/Grammar.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/JsonGrammar/Grammar.hs
@@ -0,0 +1,203 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE RankNTypes #-}
+
+module Language.JsonGrammar.Grammar (
+    Grammar(..), Context(..), (:-)(..),
+    pure, many, literal, label, object, property, array, element, coerce,
+    fromPrism, defaultValue,
+    nil, cons, tup2,
+    Json(..), el, prop
+  ) where
+
+import Prelude hiding (id, (.))
+import Control.Applicative ((<$>))
+import Control.Category (Category(..))
+import Data.Aeson (Value, FromJSON(..), ToJSON(..))
+import Data.Aeson.Types (Parser)
+import Data.Monoid (Monoid(..))
+import Data.StackPrism (StackPrism, forward, backward, (:-)(..))
+import Data.String (IsString(..))
+import Data.Text (Text)
+import Language.TypeScript (Type(..), PredefinedType(..))
+
+
+
+-- Types
+
+
+-- | The context of a grammar. Most combinators ask for a grammar in a specific context as input, and produce a grammar in another context.
+data Context
+  = Val -- ^ Value context
+  | Obj -- ^ Object context, for defining object members
+  | Arr -- ^ Array context, for defining array elements
+
+-- | A @Grammar@ provides a bidirectional mapping between a Haskell datatype and its JSON encoding. Its first type argument specifies its context: either it's defining properties (context 'Obj'), array elements (context 'Arr') or values (context 'Val').
+data Grammar (c :: Context) t1 t2 where
+  Id       :: Grammar c t t
+  (:.)     :: Grammar c t2 t3 -> Grammar c t1 t2 -> Grammar c t1 t3
+
+  Empty    :: Grammar c t1 t2
+  (:<>)    :: Grammar c t1 t2 -> Grammar c t1 t2 -> Grammar c t1 t2
+
+  Pure     :: (t1 -> Parser t2) -> (t2 -> Maybe t1) -> Grammar c t1 t2
+  Many     :: Grammar c t t -> Grammar c t t
+
+  Literal  :: Value -> Grammar Val (Value :- t) t
+
+  Label    :: Text -> Grammar Val t1 t2 -> Grammar Val t1 t2
+
+  Object   :: Grammar Obj t1 t2 -> Grammar Val (Value :- t1) t2
+  Property :: Text -> Grammar Val (Value :- t1) t2 -> Grammar Obj t1 t2
+
+  Array    :: Grammar Arr t1 t2 -> Grammar Val (Value :- t1) t2
+  Element  :: Grammar Val (Value :- t1) t2 -> Grammar Arr t1 t2
+
+  Coerce   :: Type -> Grammar Val t1 t2 -> Grammar Val t1 t2
+
+-- | The '.' operator is the main way to compose two grammars.
+instance Category (Grammar c) where
+  id = Id
+  (.) = (:.)
+
+-- | The @Monoid@ instance allows you to denote choice: if the left grammar doesn't succeed, the right grammar is tried.
+instance Monoid (Grammar c t1 t2) where
+  mempty = Empty
+  mappend = (:<>)
+
+-- | String literals convert to grammars that expect or produce a specific JSON string 'literal' value.
+instance IsString (Grammar Val (Value :- t) t) where
+  fromString = literal . fromString
+
+
+
+-- Elemental building blocks
+
+
+-- | Creates a pure grammar that doesn't specify any JSON format but just operates on the Haskell level. Pure grammars can be used in any context.
+pure :: (t1 -> Parser t2) -> (t2 -> Maybe t1) -> Grammar c t1 t2
+pure = Pure
+
+-- | Try to apply a grammar as many times as possible. The argument grammar's output is fed to itself as input until doing so again would fail. This allows you to express repetitive constructions such as array elements. 'many' can be used in any context.
+many :: Grammar c t t -> Grammar c t t
+many =  Many
+
+-- | Expect or produce a literal JSON 'Value'. You can only use this constructor in the value context 'Val'.
+literal :: Value -> Grammar Val (Value :- t) t
+literal = Literal
+
+-- | Label a value grammar with a name. This doesn't affect the JSON conversion itself, but it generates an interface definition when converting to TypeScript 'interfaces'.
+label :: Text -> Grammar Val t1 t2 -> Grammar Val t1 t2
+label = Label
+
+-- | Expect or produce a JSON object whose properties match the specified 'Obj' grammar. You can create 'Obj' grammars using 'property'. Alternatively, if you want to match an empty object, use @object 'id'@.
+object :: Grammar Obj t1 t2 -> Grammar Val (Value :- t1) t2
+object = Object
+
+-- | Expect or produce an object property with the specified name, and a value that can be parsed/produced by the specified grammar. This function creates a grammar in the 'Obj' context. You can combine multiple @property@ grammars using the '.' operator from 'Category'.
+--
+-- Use '<>' to denote choice. For example, if you are creating an object with a property called @"type"@, whose value determines what other properties your object has, you can write it like this:
+--
+-- > grammar = object (propertiesA <> propertiesB)
+-- >   where
+-- >     propertiesA = property "type" "A" . fromPiso constructorA . prop "foo"
+-- >     propertiesB = property "type" "B" . fromPiso constructorB . prop "bar" . prop "baz"
+property :: Text -> Grammar Val (Value :- t1) t2 -> Grammar Obj t1 t2
+property = Property
+
+-- | Expect or produce a JSON array value whose contents match the specified 'Arr' grammar. You can create 'Arr' grammars using 'element'. Alternatively, if you want to match an empty array, use @array 'id'@.
+array :: Grammar Arr t1 t2 -> Grammar Val (Value :- t1) t2
+array = Array
+
+-- | Expect or produce a JSON array element whose value matches the specified 'Val' grammar.
+element :: Grammar Val (Value :- t1) t2 -> Grammar Arr t1 t2
+element = Element
+
+-- | Mark a grammar to be of a specific TypeScript type. This doesn't affect the JSON conversion, but when generating TypeScript 'interfaces' a coercion causes the interface generator to stop looking at the underlying grammar and just use the specified TypeScript 'Type' as inferred type instead.
+--
+-- This is useful if you write a grammar that, for example, wraps a primitive type like string (in which case you would specify @'Predefined' 'StringType'@ as type). Another use is when you find the generated interface can't be described by a 'Grammar', for example because it uses a generic type parameter.
+coerce :: Type -> Grammar Val t1 t2 -> Grammar Val t1 t2
+coerce = Coerce
+
+
+
+-- Wrapping constructors
+
+
+-- | A 'pure' grammar that expects or produces the empty list @[]@.
+nil :: Grammar c t ([a] :- t)
+nil = Pure f g
+  where
+    f t = return ([] :- t)
+    g ([] :- t) = return t
+    g _ = fail "expected []"
+
+-- | A 'pure' grammar that expects or produces a cons ':'.
+cons :: Grammar c (a :- [a] :- t) ([a] :- t)
+cons = Pure f g
+  where
+    f (x :- xs :- t) = return ((x : xs) :- t)
+    g ((x : xs) :- t) = return (x :- xs :- t)
+    g _ = fail "expected (:)"
+
+-- | A 'pure' grammar that wraps or unwraps a tuple.
+tup2 :: Grammar c (a :- b :- t) ((a, b) :- t)
+tup2 = Pure f g
+  where
+    f (x :- y :- t) = return ((x, y) :- t)
+    g ((x, y) :- t) = return (x :- y :- t)
+
+
+
+-- Type-directed grammars
+
+
+-- | A type class for types that can be converted from and to JSON using a 'Grammar'. The grammar is expected to be in the value context 'Val' and consumes (or produces) a JSON 'Value'.
+class Json a where
+  grammar :: Grammar Val (Value :- t) (a :- t)
+
+instance Json Text  where grammar = Coerce (Predefined StringType) liftAeson
+instance Json Int   where grammar = Coerce (Predefined NumberType) liftAeson
+instance Json Float where grammar = Coerce (Predefined NumberType) liftAeson
+
+instance Json a => Json [a] where
+  grammar = Array (Many (Element (cons . grammar)) . nil)
+
+instance (Json a, Json b) => Json (a, b) where
+  grammar = tup2 . Array (Element grammar . Element grammar)
+
+
+
+-- Constructing grammars
+
+
+-- | Create a 'pure' grammar for a type that aeson already knows how to convert from/to JSON.
+liftAeson :: (FromJSON a, ToJSON a) => Grammar c (Value :- t) (a :- t)
+liftAeson = Pure f g
+  where
+    f (val :- t) = (:- t) <$> parseJSON val
+    g (x :- t) = Just (toJSON x :- t)
+
+-- | Expect or produce an object 'property' whose value grammar is specified by 'grammar'.
+prop :: Json a => Text -> Grammar Obj t (a :- t)
+prop n = Property n grammar
+
+-- | Expect or produce an array 'element' whose value grammar is specified by 'grammar'.
+el :: Json a => Grammar Arr t (a :- t)
+el = Element grammar
+
+-- | Create a 'pure' grammar that expects or produces a specific Haskell value.
+defaultValue :: Eq a => a -> Grammar c t (a :- t)
+defaultValue x = Pure f g
+  where
+    f t = return (x :- t)
+    g (x' :- t) | x == x' = Just t
+    g _ = Nothing
+
+-- | Create a 'pure' grammar from a 'StackPrism'.
+fromPrism :: StackPrism a b -> Grammar c a b
+fromPrism p = Pure (return . forward p) (backward p)
diff --git a/src/Language/JsonGrammar/Parser.hs b/src/Language/JsonGrammar/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/JsonGrammar/Parser.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE TupleSections #-}
+
+module Language.JsonGrammar.Parser (parseValue) where
+
+import Language.JsonGrammar.Grammar
+import Language.JsonGrammar.Util
+
+import Control.Applicative ((<$>))
+import Control.Monad ((>=>), unless)
+import Data.Aeson (Object, Array, withObject, (.:), withArray)
+import Data.Aeson.Types (Parser, typeMismatch)
+import Data.Monoid ((<>))
+import qualified Data.Vector as V
+
+
+-- | Convert a 'Grammar' to a JSON 'Parser'.
+parseValue :: Grammar Val t1 t2 -> t1 -> Parser t2
+parseValue = \case
+  Id        -> return
+  g1 :. g2  -> parseValue g2 >=> parseValue g1
+  Empty     -> \_ -> fail "empty grammar"
+  g1 :<> g2 -> parseValue g1 <> parseValue g2
+  Pure f _  -> f
+  Many g    -> manyM (parseValue g)
+
+  Literal val -> \(val' :- t) ->
+    if val == val'
+      then return t
+      else typeMismatch "literal" val'
+
+  Label _ g -> parseValue g
+
+  Object g -> \(val :- x) ->
+    withObject "object" (\obj -> parseProperties obj g x) val
+
+  Array g -> \(val :- x) -> do
+      (arr', y) <- withArray "array" (\arr -> parseElements g (arr, x)) val
+      unless (V.null arr') $ typeMismatch "end of array" (V.head arr')
+      return y
+
+  Coerce _ g -> parseValue g
+
+
+
+parseProperties :: Object -> Grammar Obj t1 t2 -> t1 -> Parser t2
+parseProperties obj = \case
+  Id            -> return
+  g1 :. g2     -> parseProperties obj g2 >=> parseProperties obj g1
+
+  Empty        -> \_ -> fail "empty grammar"
+  g1 :<> g2    -> parseProperties obj g1 <> parseProperties obj g2
+
+  Pure f _     -> f
+  Many g       -> manyM (parseProperties obj g)
+
+  Property n g -> \x -> do
+    val <- obj .: n
+    parseValue g (val :- x)
+
+
+parseElements :: Grammar Arr t1 t2 -> (Array, t1) -> Parser (Array, t2)
+parseElements = \case
+  Id        -> return
+  g1 :. g2  -> parseElements g2 >=> parseElements g1
+
+  Empty     -> \_ -> fail "empty grammar"
+  g1 :<> g2 -> parseElements g1 <> parseElements g2
+
+  Pure f _  -> \(arr, x) -> (arr, ) <$> f x
+  Many g    -> manyM (parseElements g)
+
+  Element g -> \(arr, x) ->
+    if V.null arr
+      then fail "expected at least one more array element"
+      else do
+        y <- parseValue g (V.last arr :- x)
+        return (V.init arr, y)
diff --git a/src/Language/JsonGrammar/Serializer.hs b/src/Language/JsonGrammar/Serializer.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/JsonGrammar/Serializer.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE TupleSections #-}
+
+module Language.JsonGrammar.Serializer (serializeValue) where
+
+import Language.JsonGrammar.Grammar
+import Language.JsonGrammar.Util
+
+import Control.Applicative ((<$>), (<|>))
+import Control.Monad ((>=>))
+import qualified Data.Aeson as Ae
+import qualified Data.HashMap.Strict as H
+import qualified Data.Vector as V
+
+
+-- | Convert a 'Grammar' to a JSON serializer.
+serializeValue :: Grammar Val t1 t2 -> t2 -> Maybe t1
+serializeValue = \case
+  Id          -> return
+  g1 :. g2    -> serializeValue g1 >=> serializeValue g2
+
+  Empty       -> fail "empty grammar"
+  g1 :<> g2   -> \x -> serializeValue g1 x <|> serializeValue g2 x
+
+  Pure _ f    -> f
+  Many g      -> manyM (serializeValue g)
+
+  Literal val -> return . (val :-)
+
+  Label _ g   -> serializeValue g
+
+  Object g    -> \x -> do
+    (obj, y) <- serializeProperties g (H.empty, x)
+    return (Ae.Object obj :- y)
+
+  Array g     -> \x -> do
+    (arr, y) <- serializeElements g (V.empty, x)
+    return (Ae.Array arr :- y)
+
+  Coerce _ g -> serializeValue g
+
+
+serializeProperties ::
+  Grammar Obj t1 t2 -> (Ae.Object, t2) -> Maybe (Ae.Object, t1)
+serializeProperties = \case
+  Id           -> return
+  g1 :. g2     -> serializeProperties g1 >=> serializeProperties g2
+
+  Empty        -> fail "empty grammar"
+  g1 :<> g2    -> \objx ->
+    serializeProperties g1 objx <|> serializeProperties g2 objx
+
+  Pure _ f     -> \(obj, x) -> (obj, ) <$> f x
+  Many g       -> manyM (serializeProperties g)
+
+  Property n g -> \(obj, x) -> do
+    val :- y <- serializeValue g x
+    return (H.insert n val obj, y)
+
+
+serializeElements :: Grammar Arr t1 t2 -> (Ae.Array, t2) -> Maybe (Ae.Array, t1)
+serializeElements = \case
+  Id        -> return
+  g1 :. g2  -> serializeElements g1 >=> serializeElements g2
+
+  Empty     -> fail "empty grammar"
+  g1 :<> g2 -> \x -> serializeElements g1 x <|> serializeElements g2 x
+
+  Pure _ f  -> \(arr, x) -> (arr, ) <$> f x
+  Many g    -> manyM (serializeElements g)
+
+  Element g -> \(arr, x) -> do
+    val :- y <- serializeValue g x
+    return (V.snoc arr val, y)
diff --git a/src/Language/JsonGrammar/TypeScript.hs b/src/Language/JsonGrammar/TypeScript.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/JsonGrammar/TypeScript.hs
@@ -0,0 +1,178 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module Language.JsonGrammar.TypeScript (SomeGrammar(..), interfaces) where
+
+import Language.JsonGrammar.Grammar
+
+import Control.Applicative ((<$>), (<*>))
+import Control.Monad (unless)
+import Control.Monad.State (State, execState, gets, modify)
+import Data.Aeson (Value)
+import qualified Data.Aeson as Ae
+import qualified Data.HashMap.Strict as H
+import Data.HashMap.Strict (HashMap)
+import Data.Text (Text)
+import qualified Data.Text as T
+import Language.TypeScript
+
+
+toType :: GrammarMap -> Grammar Val t1 t2 -> Maybe Type
+toType gm = go
+  where
+    go :: Grammar Val t1 t2 -> Maybe Type
+    go = \case
+      Id -> Nothing
+      g1 :. g2 ->
+        -- Produce the leftmost grammar
+        case go g1 of
+          Just ty -> Just ty
+          Nothing -> go g2
+
+      Empty -> Nothing
+      g1 :<> g2 -> unify <$> go g1 <*> go g2
+
+      Pure _ _ -> Nothing
+      Many g -> go g
+
+      Literal v -> Just (valueType v)
+
+      Label n _ -> Just (TypeReference (TypeRef (TypeName Nothing (T.unpack n)) Nothing))
+
+      Object g ->
+        let toSig (n, (opt, ty)) = (emptyComment,
+              PropertySignature (T.unpack n) opt (Just ty))
+        in Just (ObjectType (TypeBody (map toSig (H.toList (toProperties gm g)))))
+
+      Array g -> ArrayType <$> toElementType gm g
+
+      Coerce ty _ -> Just ty
+
+emptyComment :: CommentPlaceholder
+emptyComment = Left (0, 0)
+
+toProperties :: GrammarMap -> Grammar Obj t1 t2 -> HashMap Text (Maybe Optional, Type)
+toProperties gm = go
+  where
+    go :: Grammar Obj t1 t2 -> HashMap Text (Maybe Optional, Type)
+    go = \case
+      Id -> H.empty
+      g1 :. g2 ->
+        H.unionWith (combineTuples bothOptional unify) (go g1) (go g2)
+
+      Empty -> H.empty  -- TODO This is not the proper unit element
+      g1 :<> g2 ->
+        let props1 = go g1
+            props2 = go g2
+            markAllOptional = fmap (\(_, ty) -> (Just Optional, ty))
+         in markAllOptional (H.difference props1 props2)
+              `H.union`
+            H.intersectionWith (combineTuples eitherOptional unify) props1 props2
+              `H.union`
+            markAllOptional (H.difference props2 props1)
+
+      Pure _ _ -> H.empty
+      Many g -> go g
+
+      Property n g -> maybe H.empty (\ty -> H.singleton n (Nothing, ty)) (toType gm g)
+
+toElementType :: GrammarMap -> Grammar Arr t1 t2 -> Maybe Type
+toElementType gm = go
+  where
+    go :: Grammar Arr t1 t2 -> Maybe Type
+    go = \case
+      Id -> Nothing
+      g1 :. g2 -> unify <$> go g1 <*> go g2
+
+      Empty -> Nothing
+      g1 :<> g2 -> unify <$> go g1 <*> go g2
+
+      Pure _ _ -> Nothing
+      Many g -> go g
+
+      Element g -> toType gm g
+
+
+combineTuples :: (a1 -> a2 -> a3) -> (b1 -> b2 -> b3) ->
+                    (a1, b1) -> (a2, b2) -> (a3, b3)
+combineTuples f g (x1, y1) (x2, y2) = (f x1 x2, g y1 y2)
+
+bothOptional :: Maybe Optional -> Maybe Optional -> Maybe Optional
+bothOptional (Just Optional) (Just Optional) = Just Optional
+bothOptional _ _ = Nothing
+
+eitherOptional :: Maybe Optional -> Maybe Optional -> Maybe Optional
+eitherOptional Nothing Nothing = Nothing
+eitherOptional _ _ = Just Optional
+
+unify :: Type -> Type -> Type
+unify ty1 ty2 | areTypesEqual ty1 ty2 = ty1
+unify _ _ = Predefined AnyType
+
+areTypesEqual :: Type -> Type -> Bool
+areTypesEqual (Predefined AnyType) (Predefined AnyType) = True
+areTypesEqual (Predefined NumberType) (Predefined NumberType) = True
+areTypesEqual (Predefined BooleanType) (Predefined BooleanType) = True
+areTypesEqual (Predefined StringType) (Predefined StringType) = True
+areTypesEqual (Predefined VoidType) (Predefined VoidType) = True
+-- TODO
+areTypesEqual _ _ = False
+
+valueType :: Value -> Type
+valueType = \case
+  Ae.Object _ -> Predefined AnyType  -- TODO
+  Ae.Array _  -> Predefined AnyType  -- TODO
+  Ae.String _ -> Predefined StringType
+  Ae.Number _ -> Predefined NumberType
+  Ae.Bool _   -> Predefined BooleanType
+  Ae.Null     -> Predefined VoidType  -- TODO
+
+type GrammarMap = HashMap Text (SomeGrammar Val)
+
+grammarMap :: [SomeGrammar Val] -> GrammarMap
+grammarMap gs =
+    execState (mapM_ (\(SomeGrammar g) -> buildGrammarMap g) gs) H.empty
+  where
+    buildGrammarMap :: Grammar c t1 t2 -> State GrammarMap ()
+    buildGrammarMap = \case
+      Id        -> return ()
+      g1 :. g2  -> buildGrammarMap g1 >> buildGrammarMap g2
+
+      Empty     -> return ()
+      g1 :<> g2 -> buildGrammarMap g1 >> buildGrammarMap g2
+
+      Pure _ _  -> return ()
+      Many g    -> buildGrammarMap g
+
+      Literal _ -> return ()
+
+      Label n g -> do
+        b <- gets (H.member n)
+        unless b $ do
+          modify (H.insert n (SomeGrammar g))
+          buildGrammarMap g
+
+      Object g     -> buildGrammarMap g
+      Property _ g -> buildGrammarMap g
+
+      Array g      -> buildGrammarMap g
+      Element g    -> buildGrammarMap g
+
+      Coerce _ g   -> buildGrammarMap g
+
+-- | Wrap a @Grammar@, discarding the input/output type arguments.
+data SomeGrammar c where
+  SomeGrammar :: Grammar c t1 t2 -> SomeGrammar c
+
+-- | Generate a list of TypeScript interface declarations from the specified grammars.
+interfaces :: [SomeGrammar Val] -> [DeclarationElement]
+interfaces gs = tys
+  where
+    gm = grammarMap gs
+    tys = [ InterfaceDeclaration emptyComment Nothing interface
+          | (n, makeType -> Just (ObjectType body)) <- H.toList gm
+          , let interface = Interface emptyComment (T.unpack n) Nothing Nothing body
+          ]
+    makeType (SomeGrammar g) = toType gm g
diff --git a/src/Language/JsonGrammar/Util.hs b/src/Language/JsonGrammar/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/JsonGrammar/Util.hs
@@ -0,0 +1,6 @@
+module Language.JsonGrammar.Util where
+
+import Control.Monad ((>=>), MonadPlus(..))
+
+manyM :: (Monad m, MonadPlus m) => (a -> m a) -> a -> m a
+manyM m x = (m >=> manyM m) x `mplus` return x
diff --git a/tests/Tests.hs b/tests/Tests.hs
--- a/tests/Tests.hs
+++ b/tests/Tests.hs
@@ -1,49 +1,80 @@
 {-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE NoMonoPatBinds #-}
 
-import Types
-
-import Data.Iso
 import Language.JsonGrammar
-
-import Prelude hiding (id, (.), head, either)
-import Control.Category
+import Types
 
-import Data.Aeson (Object)
+import Prelude hiding (id, (.))
+import Control.Category (Category(..))
+import Data.Aeson.Types (Value, parseMaybe)
+import Data.Char (toLower)
+import Data.Monoid ((<>))
+import Data.StackPrism (StackPrism)
+import Data.StackPrism.TH (deriveStackPrismsWith)
+import Language.TypeScript (renderDeclarationSourceFile)
 import Test.Framework (Test, defaultMain)
 import Test.Framework.Providers.HUnit (testCase)
-import Test.HUnit (assertEqual)
+import Test.HUnit (assertBool)
 
 
-person         = $(deriveIsos ''Person)
-(male, female) = $(deriveIsos ''Gender)
-coords         = $(deriveIsos ''Coords)
+-- Create stack prisms for our three datatypes (defined in module Types)
 
+deriveStackPrismsWith (map toLower) ''Person
+deriveStackPrismsWith (map toLower) ''Coords
+deriveStackPrismsWith (map toLower) ''Gender
 
+
+-- Write Json instances for the datatypes
+
+instance Json Gender where
+  grammar = fromPrism male   . literal "male"
+         <> fromPrism female . literal "female"
+
 instance Json Person where
-  grammar = person . object
-    ( prop "naam"
-    . prop "geslacht"
-    . prop "leeftijd"
-    . coordsProps
+  grammar = label "Person" $
+    fromPrism person . object
+    ( prop "name"
+    . prop "gender"
+    . (prop "age" <> defaultValue 42)
+    . fromPrism coords
+    . property "coords" (array (el . el))
     )
 
-instance Json Gender where
-  grammar = male   . litJson "man"
-         <> female . litJson "vrouw"
 
-coordsProps :: Iso (Object :- t) (Object :- Coords :- t)
-coordsProps = duck coords . prop "lat" . prop "lng"
+-- Create two example values
 
-anna :: Person
-anna = Person "Anna" Female 36 (Coords 53.0163038 5.1993053)
+alice :: Person
+alice = Person "Alice" Female 21 (Coords 52 5)
 
-main :: IO ()
-main = defaultMain [personTest]
+bob :: Person
+bob = Person "Bob" Male 22 (Coords 53 6)
 
-personTest :: Test
-personTest = testCase "Person" (assertEqual "" anna anna')
+
+-- Two tests: one for lists, the other for tuples
+
+test1, test2 :: Test
+test1 = testCase "PersonList"  $ assertBool "" (checkInverse [alice, bob])
+test2 = testCase "PersonTuple" $ assertBool "" (checkInverse (alice, bob))
+
+checkInverse :: (Json a, Eq a) => a -> Bool
+checkInverse value = value == value'
   where
-    Just anna' = fromJson annaJson
-    Just annaJson = toJson anna
+    Just json   = serialize grammar value
+    Just value' = parseMaybe (parse grammar) json
+
+
+-- Write the TypeScript definition to stdout and run the tests
+
+main :: IO ()
+main = do
+  printInterfaces [SomeGrammar personGrammar]
+  defaultMain [test1, test2]
+
+personGrammar :: Grammar Val (Value :- t) (Person :- t)
+personGrammar = grammar
+
+printInterfaces :: [SomeGrammar Val] -> IO ()
+printInterfaces gs = putStrLn (renderDeclarationSourceFile (interfaces gs))
diff --git a/tests/Types.hs b/tests/Types.hs
new file mode 100644
--- /dev/null
+++ b/tests/Types.hs
@@ -0,0 +1,15 @@
+module Types where
+
+import Data.Text (Text)
+
+data Person = Person
+  { name     :: Text
+  , gender   :: Gender
+  , age      :: Int
+  , location :: Coords
+  } deriving (Show, Eq)
+
+data Coords = Coords { lat :: Float, lng :: Float }
+  deriving (Show, Eq)
+
+data Gender = Male | Female deriving (Show, Eq)
