diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for hexomorph
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+# Haskell Schema Aeson
+
+This a companion package for the `hschema` package providing JSON encoder and decoders for your data types.
+Find more information on how to use this in the `hschema` package.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hschema-aeson.cabal b/hschema-aeson.cabal
new file mode 100644
--- /dev/null
+++ b/hschema-aeson.cabal
@@ -0,0 +1,100 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 71f8e3ffc184bb6433eed64355bc3b9cf9e12302dadf85d86df8ebf8d4f254f7
+
+name:           hschema-aeson
+version:        0.0.1.0
+synopsis:       Describe schemas for your Haskell data types.
+description:    Please see the README on GitHub at <https://github.com/alonsodomin/haskell-schema#readme>
+category:       Data,Schema,JSON
+homepage:       https://github.com/alonsodomin/haskell-schema#readme
+bug-reports:    https://github.com/alonsodomin/haskell-schema/issues
+author:         Antonio Alonso Dominguez
+maintainer:     alonso.domin@gmail.com
+copyright:      2018 Antonio Alonso Dominguez
+license:        LGPL-3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/alonsodomin/haskell-schema
+
+library
+  exposed-modules:
+      Data.Schema.JSON
+      Data.Schema.JSON.Internal.Serializer
+      Data.Schema.JSON.Internal.Types
+      Data.Schema.JSON.Simple
+  other-modules:
+      Paths_hschema_aeson
+  hs-source-dirs:
+      src
+  build-depends:
+      QuickCheck
+    , aeson
+    , base >=4.7 && <5
+    , comonad >=5.0 && <5.1
+    , contravariant
+    , free
+    , hschema >=0.0.1.0 && <0.0.2.0
+    , hschema-prettyprinter >=0.0.1.0 && <0.0.2.0
+    , hschema-quickcheck >=0.0.1.0 && <0.0.2.0
+    , lens
+    , mtl
+    , natural-transformation
+    , prettyprinter
+    , prettyprinter-ansi-terminal
+    , quickcheck-instances
+    , scientific
+    , text
+    , time
+    , unordered-containers
+    , vector
+  default-language: Haskell2010
+
+test-suite hschema-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Test.Schema.JSON
+      Test.Schema.Model
+      Test.Schema.Utils
+      Paths_hschema_aeson
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      QuickCheck
+    , aeson
+    , base >=4.7 && <5
+    , bytestring
+    , comonad >=5.0 && <5.1
+    , contravariant
+    , convertible
+    , directory
+    , free
+    , hschema
+    , hschema-aeson
+    , hschema-prettyprinter >=0.0.1.0 && <0.0.2.0
+    , hschema-quickcheck >=0.0.1.0 && <0.0.2.0
+    , hspec
+    , lens
+    , mtl
+    , natural-transformation
+    , prettyprinter
+    , prettyprinter-ansi-terminal
+    , quickcheck-instances
+    , scientific
+    , text
+    , time
+    , unordered-containers
+    , vector
+  default-language: Haskell2010
diff --git a/src/Data/Schema/JSON.hs b/src/Data/Schema/JSON.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Schema/JSON.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE FlexibleContexts     #-}
+{-# LANGUAGE FlexibleInstances    #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Data.Schema.JSON
+     ( JsonType
+     , JsonSchema
+     , JsonField
+     , JsonSerializer(..)
+     , JsonDeserializer(..)
+     , ToJsonSerializer(..)
+     , ToJsonDeserializer(..)
+     , JsonPrimitive(..)
+     ) where
+
+import           Data.Aeson                           (FromJSON (parseJSON),
+                                                       ToJSON (toJSON))
+import           Data.Schema
+import           Data.Schema.JSON.Internal.Serializer
+import           Data.Schema.JSON.Internal.Types
+
+instance (HasSchema a, ToJsonSerializer (PrimitivesOf a)) => ToJSON a where
+  toJSON = runJsonSerializer . toJsonSerializer $ getSchema
+
+instance (HasSchema a, ToJsonDeserializer (PrimitivesOf a)) => FromJSON a where
+  parseJSON = runJsonDeserializer . toJsonDeserializer $ getSchema
diff --git a/src/Data/Schema/JSON/Internal/Serializer.hs b/src/Data/Schema/JSON/Internal/Serializer.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Schema/JSON/Internal/Serializer.hs
@@ -0,0 +1,107 @@
+{-# LANGUAGE FlexibleContexts     #-}
+{-# LANGUAGE FlexibleInstances    #-}
+{-# LANGUAGE GADTs                #-}
+{-# LANGUAGE LambdaCase           #-}
+{-# LANGUAGE TypeOperators        #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Data.Schema.JSON.Internal.Serializer where
+
+import           Control.Applicative.Free
+import           Control.Functor.HigherOrder
+import           Control.Lens                hiding (iso)
+import           Control.Monad.State         (State)
+import qualified Control.Monad.State         as ST
+import           Control.Natural
+import qualified Data.Aeson.Types            as JSON
+import           Data.Functor.Contravariant
+import           Data.Functor.Sum
+import           Data.HashMap.Strict         (HashMap)
+import qualified Data.HashMap.Strict         as Map
+import           Data.List.NonEmpty          (NonEmpty)
+import qualified Data.List.NonEmpty          as NEL
+import           Data.Maybe
+import           Data.Schema.Internal.Types
+import           Data.Text                   (Text)
+
+newtype JsonSerializer a = JsonSerializer { runJsonSerializer :: a -> JSON.Value }
+
+instance Contravariant JsonSerializer where
+  contramap f (JsonSerializer g) = JsonSerializer $ g . f
+
+newtype JsonDeserializer a = JsonDeserializer { runJsonDeserializer :: JSON.Value -> JSON.Parser a }
+
+instance Functor JsonDeserializer where
+  fmap f (JsonDeserializer g) = JsonDeserializer $ \x -> fmap f (g x)
+
+instance Applicative JsonDeserializer where
+  pure x = JsonDeserializer $ \_ -> pure x
+  (JsonDeserializer l) <*> (JsonDeserializer r) = JsonDeserializer $ \x -> (l x) <*> (r x)
+
+class ToJsonSerializer s where
+  toJsonSerializer :: s ~> JsonSerializer
+
+class ToJsonDeserializer s where
+  toJsonDeserializer :: s ~> JsonDeserializer
+
+instance (ToJsonSerializer p, ToJsonSerializer q) => ToJsonSerializer (Sum p q) where
+  toJsonSerializer (InL l) = toJsonSerializer l
+  toJsonSerializer (InR r) = toJsonSerializer r
+
+toJsonSerializerAlg :: ToJsonSerializer p => HAlgebra (SchemaF p) JsonSerializer
+toJsonSerializerAlg = wrapNT $ \case
+  PrimitiveSchema p -> toJsonSerializer p
+
+  RecordSchema fields -> JsonSerializer $ \obj -> JSON.Object $ ST.execState (runAp (encodeFieldOf obj) (unwrapField fields)) Map.empty
+    where encodeFieldOf :: o -> FieldDef o JsonSerializer v -> State (HashMap Text JSON.Value) v
+          encodeFieldOf o (RequiredField name (JsonSerializer serialize) getter) = do
+            let el = view getter o
+            ST.modify $ Map.insert name (serialize el)
+            return el
+          encodeFieldOf o (OptionalField name (JsonSerializer serialize) getter) = do
+            let el = view getter o
+            ST.modify $ Map.insert name (maybe JSON.Null serialize el)
+            return el
+
+  UnionSchema alts -> JsonSerializer $ \value -> head . catMaybes . NEL.toList $ fmap (encodeAlt value) alts
+    where singleAttrObj :: Text -> JSON.Value -> JSON.Value
+          singleAttrObj n v = JSON.Object $ Map.insert n v Map.empty
+
+          encodeAlt :: o -> AltDef JsonSerializer o -> Maybe JSON.Value
+          encodeAlt o (AltDef name (JsonSerializer serialize) pr) = do
+            json <- serialize <$> o ^? pr
+            return $ singleAttrObj name json
+
+  AliasSchema (JsonSerializer base) iso -> JsonSerializer $ \value -> base (view (re iso) value)
+
+instance ToJsonSerializer p => ToJsonSerializer (Schema p) where
+  toJsonSerializer schema = (cataNT toJsonSerializerAlg) (unwrapSchema schema)
+
+instance (ToJsonDeserializer p, ToJsonDeserializer q) => ToJsonDeserializer (Sum p q) where
+  toJsonDeserializer (InL l) = toJsonDeserializer l
+  toJsonDeserializer (InR r) = toJsonDeserializer r
+
+toJsonDeserializerAlg :: ToJsonDeserializer p => HAlgebra (SchemaF p) JsonDeserializer
+toJsonDeserializerAlg = wrapNT $ \case
+  PrimitiveSchema p -> toJsonDeserializer p
+
+  RecordSchema fields -> JsonDeserializer $ \json -> case json of
+    JSON.Object obj -> runAp decodeField $ unwrapField fields
+      where decodeField :: FieldDef o JsonDeserializer v -> JSON.Parser v
+            decodeField (RequiredField name (JsonDeserializer deserial) _) = JSON.explicitParseField deserial obj name
+            decodeField (OptionalField name (JsonDeserializer deserial) _) = JSON.explicitParseFieldMaybe deserial obj name
+    other -> fail $ "Expected JSON Object but got: " ++ (show other)
+
+  UnionSchema alts -> JsonDeserializer $ \json -> case json of
+    JSON.Object obj -> head . catMaybes . NEL.toList $ fmap lookupParser alts
+      where lookupParser :: AltDef JsonDeserializer a -> Maybe (JSON.Parser a)
+            lookupParser (AltDef name (JsonDeserializer deserial) pr) = do
+              altParser <- deserial <$> Map.lookup name obj
+              return $ (view $ re pr) <$> altParser
+    other ->  fail $ "Expected JSON Object but got: " ++ (show other)
+
+  AliasSchema (JsonDeserializer base) iso -> JsonDeserializer $ \json -> (view iso) <$> (base json)
+
+instance ToJsonDeserializer p => ToJsonDeserializer (Schema p) where
+  toJsonDeserializer schema = (cataNT toJsonDeserializerAlg) (unwrapSchema schema)
diff --git a/src/Data/Schema/JSON/Internal/Types.hs b/src/Data/Schema/JSON/Internal/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Schema/JSON/Internal/Types.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE FlexibleInstances    #-}
+{-# LANGUAGE GADTs                #-}
+{-# LANGUAGE KindSignatures       #-}
+{-# LANGUAGE LambdaCase           #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+module Data.Schema.JSON.Internal.Types where
+
+import           Control.Applicative                  (liftA2)
+import           Control.Functor.HigherOrder
+import           Data.Aeson                           (parseJSON)
+import qualified Data.Aeson.Types                     as JSON
+import           Data.HashMap.Strict                  (HashMap)
+import qualified Data.HashMap.Strict                  as Map
+import           Data.Schema.Internal.Types
+import           Data.Schema.JSON.Internal.Serializer
+import           Data.Schema.PrettyPrint
+import           Data.Scientific
+import           Data.Text                            (Text)
+import qualified Data.Text                            as T
+import           Data.Text.Prettyprint.Doc            ((<+>))
+import qualified Data.Text.Prettyprint.Doc            as PP
+import           Data.Vector                          (Vector)
+import qualified Data.Vector                          as Vector
+import qualified Test.QuickCheck                      as QC
+import qualified Test.QuickCheck.Gen                  as QC
+import           Test.QuickCheck.Instances.Scientific ()
+import           Test.Schema.QuickCheck.Internal.Gen
+
+data JsonPrimitive (f :: (* -> *)) (a :: *) where
+  JsonNumber :: JsonPrimitive f Scientific
+  JsonText   :: JsonPrimitive f Text
+  JsonBool   :: JsonPrimitive f Bool
+  JsonArray  :: f a -> JsonPrimitive f (Vector a)
+  JsonMap    :: f a -> JsonPrimitive f (HashMap Text a)
+
+type JsonType = HMutu JsonPrimitive Schema
+
+-- | Simple JSON schema type
+type JsonSchema = Schema JsonType
+
+-- | Simple JSON field type
+type JsonField o a = Field JsonSchema o a
+
+instance ToJsonSerializer JsonType where
+  toJsonSerializer jType = JsonSerializer $ case (unmutu jType) of
+    JsonNumber      -> JSON.Number
+    JsonText        -> JSON.String
+    JsonBool        -> JSON.Bool
+    JsonArray value -> \x ->
+      JSON.Array $ fmap (runJsonSerializer . toJsonSerializer $ value) x
+    JsonMap value   -> \x ->
+      JSON.Object $ Map.map (runJsonSerializer . toJsonSerializer $ value) x
+
+instance ToJsonDeserializer JsonType where
+  toJsonDeserializer jType = JsonDeserializer $ case (unmutu jType) of
+    JsonNumber      -> parseJSON
+    JsonText        -> parseJSON
+    JsonBool        -> parseJSON
+    JsonArray value -> \case
+      JSON.Array arr -> traverse (runJsonDeserializer . toJsonDeserializer $ value) arr
+      other          -> fail $ "Expected a JSON array but got: " ++ (show other)
+    JsonMap value   -> \case
+      JSON.Object obj -> Map.foldrWithKey Map.insert Map.empty <$> traverse (runJsonDeserializer . toJsonDeserializer $ value) obj
+      other           -> fail $ "Expected a JSON object but got: " ++ (show other)
+
+instance ToGen JsonType where
+  toGen jType = case (unmutu jType) of
+    JsonNumber      -> QC.arbitrary
+    JsonText        -> T.pack <$> (QC.listOf QC.chooseAny)
+    JsonBool        -> QC.arbitrary :: (QC.Gen Bool)
+    JsonArray value -> Vector.fromList <$> QC.listOf (toGen value)
+    JsonMap value   -> Map.fromList <$> (QC.listOf $ liftA2 ((,)) (T.pack <$> (QC.listOf QC.chooseAny)) (toGen value))
+
+instance ToSchemaDoc JsonType where
+  toSchemaDoc jType = SchemaDoc $ case (unmutu jType) of
+    JsonNumber      -> PP.pretty "Number"
+    JsonText        -> PP.pretty "Text"
+    JsonBool        -> PP.pretty "Bool"
+    JsonArray value -> PP.pretty "[" <> (getDoc . toSchemaDoc $ value) <> PP.pretty "]"
+    JsonMap value   -> PP.pretty "Map { Text ->" <+> (getDoc . toSchemaDoc $ value) <+> PP.pretty "}"
+
+instance ToSchemaLayout JsonType where
+  toSchemaLayout jType = SchemaLayout $ case (unmutu jType) of
+    JsonNumber      -> PP.unsafeViaShow
+    JsonText        -> PP.unsafeViaShow
+    JsonBool        -> PP.unsafeViaShow
+    JsonArray value -> \x ->
+      PP.vsep $ fmap (\v -> runSchemaLayout (toSchemaLayout value) v) $ Vector.toList x
+    JsonMap value   -> \x ->
+      PP.vsep $ fmap (\(k,v) -> PP.pretty k <+> PP.pretty "->" <+> runSchemaLayout (toSchemaLayout value) v) $ Map.toList x
diff --git a/src/Data/Schema/JSON/Simple.hs b/src/Data/Schema/JSON/Simple.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Schema/JSON/Simple.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE GADTs               #-}
+{-# LANGUAGE LambdaCase          #-}
+{-# LANGUAGE LiberalTypeSynonyms #-}
+{-# LANGUAGE RankNTypes          #-}
+
+module Data.Schema.JSON.Simple where
+
+import           Control.Functor.HigherOrder
+import           Control.Lens
+import           Data.HashMap.Strict             (HashMap)
+import           Data.Schema
+import           Data.Schema.Internal.Types
+import           Data.Schema.JSON.Internal.Types
+import           Data.Scientific
+import           Data.Text                       (Text)
+import qualified Data.Text                       as T
+import           Data.Vector                     (Vector)
+
+-- | Define a text primitive
+text :: JsonSchema Text
+text = prim $ HMutu JsonText
+
+-- | Define a string primitive
+string :: JsonSchema String
+string = alias (iso T.unpack T.pack) text
+
+-- | Define a scientific number primitive
+number :: JsonSchema Scientific
+number = prim $ HMutu JsonNumber
+
+-- | Define an integral primitive
+int :: Integral a => JsonSchema a
+int = alias (iso (\x -> either truncate id $ floatingOrInteger x) fromIntegral) number
+
+-- | Define a floating point primitive
+real :: RealFloat a => JsonSchema a
+real = alias (iso (\x -> either id fromIntegral $ floatingOrInteger x) fromFloatDigits) number
+
+array :: JsonSchema a -> JsonSchema (Vector a)
+array elemSchema = prim $ HMutu (JsonArray elemSchema)
+
+list :: JsonSchema a -> JsonSchema [a]
+list = toList . array
+
+hash :: JsonSchema a -> JsonSchema (HashMap Text a)
+hash elemSchema = prim $ HMutu (JsonMap elemSchema)
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,4 @@
+import           Test.Schema.JSON
+
+main :: IO ()
+main = verifyJsonSchema
diff --git a/test/Test/Schema/JSON.hs b/test/Test/Schema/JSON.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Schema/JSON.hs
@@ -0,0 +1,45 @@
+module Test.Schema.JSON
+     ( verifyJsonSchema
+     ) where
+
+import           Data.Aeson
+import           Data.Aeson.Text
+import           Data.Convertible
+import           Data.Schema.JSON
+import qualified Data.Text.Lazy    as T
+import           Test.Hspec
+import           Test.QuickCheck
+import           Test.Schema.Model
+import           Test.Schema.Utils
+
+samplePerson :: Person
+samplePerson = Person "foo" (Just $ convert (12 :: Int)) [
+    mkUserRole
+  , mkAdminRole "bar" 4
+  ]
+
+samplePersonJSONFileName :: String
+samplePersonJSONFileName = "expected-model.json"
+
+samplePersonJSON :: IO String
+samplePersonJSON = loadTestFile samplePersonJSONFileName
+
+prop_reverse :: Person -> Bool
+prop_reverse person = decode (encode person) == (Just person)
+
+describeJsonSerialization :: IO ()
+describeJsonSerialization = hspec $ do
+  describe "toJsonSerializer" $ do
+    it "should generate valid JSON" $ do
+      expectedJSON <- samplePersonJSON
+      (T.unpack $ encodeToLazyText samplePerson) `shouldBe` expectedJSON
+
+    it "should parse the given JSON" $ do
+      givenJSON     <- samplePersonJSON
+      decodedPerson <- decodeFileStrict =<< testFilePath samplePersonJSONFileName
+      decodedPerson `shouldBe` (Just samplePerson)
+
+verifyJsonSchema :: IO ()
+verifyJsonSchema = do
+  quickCheck prop_reverse
+  describeJsonSerialization
diff --git a/test/Test/Schema/Model.hs b/test/Test/Schema/Model.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Schema/Model.hs
@@ -0,0 +1,75 @@
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies      #-}
+
+module Test.Schema.Model where
+
+import           Control.Lens
+import           Data.Aeson
+import           Data.Convertible
+import           Data.Schema             (HasSchema (..))
+import qualified Data.Schema             as S
+import           Data.Schema.JSON
+import qualified Data.Schema.JSON.Simple as JSON
+import           Data.Time               (UTCTime)
+import           Test.QuickCheck
+import           Test.Schema.QuickCheck
+
+utcTimeSchema :: JsonSchema UTCTime
+utcTimeSchema = S.alias (iso convert convert) (JSON.int :: JsonSchema Integer)
+
+data Role =
+    UserRole UserRole
+  | AdminRole AdminRole
+  deriving (Eq, Show)
+
+data UserRole = UserRole'
+  deriving (Eq, Show)
+
+data AdminRole = AdminRole' { department :: String, subordinateCount :: Int }
+  deriving (Eq, Show)
+
+mkUserRole :: Role
+mkUserRole = UserRole $ UserRole'
+
+mkAdminRole :: String -> Int -> Role
+mkAdminRole dpt subs = AdminRole $ AdminRole' dpt subs
+
+_UserRole :: Prism' Role UserRole
+_UserRole = prism' UserRole $ \case
+    UserRole x -> Just x
+    _          -> Nothing
+
+_AdminRole :: Prism' Role AdminRole
+_AdminRole = prism' AdminRole $ \case
+    AdminRole x -> Just x
+    _           -> Nothing
+
+adminRole :: JsonSchema AdminRole
+adminRole = S.record
+          ( AdminRole'
+          <$> S.field "department"       JSON.string (to department)
+          <*> S.field "subordinateCount" JSON.int    (to subordinateCount)
+          )
+
+roleSchema :: JsonSchema Role
+roleSchema = S.oneOf
+           [ S.alt "user"  (S.const UserRole') _UserRole
+           , S.alt "admin" adminRole           _AdminRole
+           ]
+
+data Person = Person { personName :: String, birthDate :: Maybe UTCTime, roles :: [Role] }
+  deriving (Eq, Show)
+
+personSchema :: JsonSchema Person
+personSchema = S.record
+             ( Person
+             <$> S.field    "name"      JSON.string            (to personName)
+             <*> S.optional "birthDate" utcTimeSchema          (to birthDate)
+             <*> S.field    "roles"     (JSON.list roleSchema) (to roles)
+             )
+
+instance HasSchema Person where
+  type PrimitivesOf Person = JsonType
+
+  getSchema = personSchema
diff --git a/test/Test/Schema/Utils.hs b/test/Test/Schema/Utils.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Schema/Utils.hs
@@ -0,0 +1,17 @@
+module Test.Schema.Utils where
+
+import           System.Directory
+import           System.IO
+
+getTestFolder :: IO FilePath
+getTestFolder = do
+  baseDir <- getCurrentDirectory
+  return $ baseDir ++ "/test/"
+
+testFilePath :: String -> IO String
+testFilePath f = do
+  testDir <- getTestFolder
+  return $ testDir ++ f
+
+loadTestFile :: String -> IO String
+loadTestFile f = testFilePath f >>= readFile
