diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Julian K. Arni
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Julian K. Arni nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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/examples/Example.hs b/examples/Example.hs
new file mode 100644
--- /dev/null
+++ b/examples/Example.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeOperators     #-}
+import qualified Data.Map     as Map
+import           Data.Proxy   (Proxy (..))
+import           GHC.Generics (Generic)
+
+import           Verdict
+import           Verdict.JSON
+
+type NameC = MinLength 1 :&& MaxLength 100
+type Name = Validated NameC String
+type AgeC = Minimum 0 :&& Maximum 200
+type Age  = Validated AgeC Integer
+
+data Person = Person
+    { name :: Name
+    , age  :: Age
+    } deriving (Eq, Show, Read, Generic)
+
+instance JsonSchema Person where
+    jsonSchema _ = JsonSpec $ Map.fromList [ ("name", Left $ jsonVerdict namep)
+                                           , ("age" , Left $ jsonVerdict agep )
+                                           ]
+      where namep = Proxy :: Proxy NameC
+            agep  = Proxy :: Proxy AgeC
+
+main :: IO ()
+main = print $ jsonSchema (Proxy :: Proxy Person)
diff --git a/src/Verdict/JSON.hs b/src/Verdict/JSON.hs
new file mode 100644
--- /dev/null
+++ b/src/Verdict/JSON.hs
@@ -0,0 +1,21 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Verdict.JSON
+    ( JsonSchema(..)
+    , ObjectSchema(..)
+    , AnySchema(..)
+    , Required(..)
+    , mkAny
+    , jsonSchema
+    ) where
+
+import Data.Aeson
+import Verdict
+
+import Verdict.JSON.Class
+import Verdict.JSON.Types
+
+instance (HaskVerdict c a, FromJSON a) => FromJSON (Validated c a) where
+    parseJSON x = parseJSON x >>= either (fail . show) return . validate
+
+instance (ToJSON a) => ToJSON (Validated c a) where
+    toJSON = toJSON . getVal
diff --git a/src/Verdict/JSON/Class.hs b/src/Verdict/JSON/Class.hs
new file mode 100644
--- /dev/null
+++ b/src/Verdict/JSON/Class.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module Verdict.JSON.Class where
+
+import           Data.Monoid
+import           Data.Proxy
+import           GHC.TypeLits
+import           Verdict
+import           Verdict.JSON.Types
+
+
+------------------------------------------------------------------------------
+-- * JsonSchema
+------------------------------------------------------------------------------
+
+class (MkAny (JsonType a), Monoid (JsonType a)) => JsonSchema a where
+    type JsonType a
+    jsonSchema' :: proxy a -> JsonType a
+
+instance (KnownNat n)
+         => JsonSchema (Validated (Maximum n) i) where
+    type JsonType (Validated (Maximum n) i) = NumericSchema
+    jsonSchema' _ = mempty { maximum' = Just $ Max v }
+      where v = fromInteger $ natVal (Proxy :: Proxy n)
+
+instance (KnownNat n)
+         => JsonSchema (Validated (Minimum n) i) where
+    type JsonType (Validated (Minimum n) i) = NumericSchema
+    jsonSchema' _ = mempty { minimum' = Just $ Min v }
+      where v = fromInteger $ natVal (Proxy :: Proxy n)
+
+instance ( JsonSchema (Validated c a), JsonSchema (Validated c' a)
+         , JsonType (Validated c a) ~ JsonType (Validated c' a)
+         ) => JsonSchema (Validated (c :&& c') a) where
+    type JsonType (Validated (c :&& c') a) = JsonType (Validated c a)
+    jsonSchema' _ = jsonSchema' pa <> jsonSchema' pb
+      where pa = Proxy :: Proxy (Validated c a)
+            pb = Proxy :: Proxy (Validated c' a)
+
+instance ( KnownNat n
+         ) => JsonSchema (Validated (MaxLength n) String) where
+    type JsonType (Validated (MaxLength n) String) = StringSchema
+    jsonSchema' _ = mempty { maxLength = Just $ Max v }
+      where v = fromInteger $ natVal (Proxy :: Proxy n)
+
+instance ( KnownNat n
+         ) => JsonSchema (Validated (MinLength n) String) where
+    type JsonType (Validated (MinLength n) String) = StringSchema
+    jsonSchema' _ = mempty { minLength = Just $ Min v }
+      where v = fromInteger $ natVal (Proxy :: Proxy n)
+
+class MkAny a where
+    mkAny :: a -> AnySchema
+
+instance MkAny ObjectSchema where
+    mkAny = ObjectS
+
+instance MkAny NumericSchema where
+    mkAny = NumericS
+
+instance MkAny StringSchema where
+    mkAny = StringS
+
+jsonSchema :: JsonSchema a => proxy a -> AnySchema
+jsonSchema = mkAny . jsonSchema'
diff --git a/src/Verdict/JSON/Types.hs b/src/Verdict/JSON/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Verdict/JSON/Types.hs
@@ -0,0 +1,138 @@
+{-# LANGUAGE DeriveAnyClass    #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Verdict.JSON.Types where
+
+import           Data.Aeson
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.Map            as Map
+import           Data.Maybe
+import           Data.Monoid
+import qualified Data.Text           as Text
+import           Data.Vector         (fromList)
+import           GHC.Generics        (Generic)
+
+data NumericT = JSONInteger | JSONNumeric
+  deriving (Eq, Show, Read, Generic)
+
+data AnySchema = ObjectS ObjectSchema
+               | NumericS NumericSchema
+               {-| ArrayS ArraySchema-}
+               | StringS StringSchema
+               | EmptyS
+  deriving (Eq, Show, Read, Generic)
+
+instance Monoid AnySchema where
+    mempty = EmptyS
+    (ObjectS a)  `mappend` (ObjectS b)  = ObjectS (a <> b)
+    (NumericS a) `mappend` (NumericS b) = NumericS (a <> b)
+    (StringS a)  `mappend` (StringS b)  = StringS (a <> b)
+    EmptyS       `mappend` x            = x
+    _            `mappend` _            = error "must be same constructor"
+
+instance ToJSON AnySchema where
+    toJSON (ObjectS os)  = let (Object o) = toJSON os
+                           in Object $ HashMap.insert "type" (String "object") o
+    toJSON (NumericS ns) = let (Object o) = toJSON ns
+                           in Object $ HashMap.insert "type" (String "number") o
+    toJSON (StringS ss)  = let (Object o) = toJSON ss
+                           in Object $ HashMap.insert "type" (String "string") o
+    toJSON EmptyS        = object []
+
+data Either' a b = Left' a | Right' b
+    deriving (Eq, Show, Read, Functor, Generic)
+
+instance (ToJSON a, ToJSON b) => ToJSON (Either' a b) where
+    toJSON (Left' a)  = toJSON a
+    toJSON (Right' b) = toJSON b
+
+data ObjectSchema = ObjectSchema
+    { properties           :: Map.Map Text.Text (Required, AnySchema)
+    , additionalProperties :: ()
+    , patternProperties    :: Map.Map Text.Text AnySchema
+    } deriving (Eq, Show, Read, Generic)
+
+instance Monoid ObjectSchema where
+    mempty = ObjectSchema mempty mempty mempty
+    a `mappend` b = ObjectSchema
+        { properties           = properties a <> properties b
+        , additionalProperties = mempty
+        , patternProperties    = patternProperties a <> patternProperties b
+        }
+
+instance ToJSON ObjectSchema where
+    toJSON os = object [
+        "properties"           .= toJSON (snd <$> properties os)
+      , "required"             .= Array (String <$> fromList reqs)
+      {-, "additionalProperties" .= toJSON (additionalProperties os)-}
+      {-, "patternProperties"    .= toJSON (patternProperties os)-}
+      ]
+      where reqs = Map.keys $ Map.filter ((== Required) . fst) $ properties os
+
+data NumericSchema = NumericSchema
+    { multipleOf  :: [Int]
+    , maximum'    :: Maybe Max
+    , minimum'    :: Maybe Min
+    } deriving (Eq, Show, Read, Generic)
+
+instance Monoid NumericSchema where
+    mempty = NumericSchema mempty mempty mempty
+    a `mappend` b = NumericSchema { multipleOf = multipleOf a <> multipleOf b
+                                  , maximum' = maximum' a <> maximum' b
+                                  , minimum' = minimum' a <> minimum' b
+                                  }
+
+instance ToJSON NumericSchema where
+    toJSON ns = object $ catMaybes go
+      where
+        go = [ ("multipleOf" .=) <$> (toJSON <$> listToMaybe (multipleOf ns))
+             , ("maximum"    .=) <$> (toJSON . unMax <$> maximum' ns)
+             , ("minimum"    .=) <$> (toJSON . unMin <$> minimum' ns)
+             ]
+
+newtype Max = Max { unMax :: Int}
+    deriving (Eq, Show, Bounded, Ord, Read, Generic)
+
+instance Monoid Max where
+    mempty  = minBound
+    mappend = max
+
+newtype Min = Min { unMin :: Int }
+    deriving (Eq, Show, Bounded, Ord, Read, Generic)
+
+instance Monoid Min where
+    mempty  = maxBound
+    mappend = min
+
+data ArraySchema = ArraySchema
+    { items           :: [AnySchema]
+    , additionalItems :: Either' Bool AnySchema
+    } deriving (Eq, Show, Read, Generic)
+
+data StringSchema = StringSchema
+    { maxLength :: Maybe Max
+    , minLength :: Maybe Min
+    {-, pattern   :: Maybe Regex-}
+    } deriving (Eq, Show, Read, Generic)
+
+instance Monoid StringSchema where
+    mempty = StringSchema mempty mempty
+    a `mappend` b = StringSchema { maxLength = maxLength a <> maxLength b
+                                 , minLength = minLength a <> minLength b
+                                 }
+
+instance ToJSON StringSchema where
+    toJSON ss = object $
+        catMaybes [ ("maxLength" .=) <$> (toJSON . unMax <$> maxLength ss)
+                  , ("minLength" .=) <$> (toJSON . unMin <$> minLength ss)
+                  ]
+
+data Metadata = Metadata
+    { title       :: Maybe Text.Text
+    , description :: Maybe Text.Text
+    } deriving (Eq, Show, Read, Generic)
+
+data SchemaVersion = Draft4
+  deriving (Eq, Show, Read, Generic)
+
+data Required = Required | NotRequired
+  deriving (Eq, Show, Read, Generic)
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/Verdict/JSONSpec.hs b/test/Verdict/JSONSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Verdict/JSONSpec.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-unused-binds #-}
+module Verdict.JSONSpec (spec) where
+
+import           Data.Aeson
+import           Data.Foldable (toList)
+import qualified Data.Map     as Map
+import qualified Data.HashMap.Strict as HashMap
+import           Data.Proxy
+import           Data.Vector  (fromList)
+import           GHC.Generics (Generic)
+import           Test.Hspec   (Spec, describe, it, shouldBe, shouldContain, context)
+import           Verdict
+
+import           Verdict.JSON
+
+spec :: Spec
+spec = describe "Verdict.JSON" $ do
+    fromJSONSpec
+    specSpec
+
+
+fromJSONSpec :: Spec
+fromJSONSpec = describe "FromJSON instance" $ do
+
+  it "does validation when parsing" $ do
+    (decode "5" :: Maybe EvenInt) `shouldBe` Nothing
+
+  it "gives a useful error message" $ do
+    let Left e = eitherDecode "5" :: Either String EvenInt
+    e `shouldContain` "Not a multiple of 2"
+
+  it "parses valid values" $ do
+    let (Right expected) = validate 4
+    (decode "4" :: Maybe EvenInt) `shouldBe` Just expected
+
+specSpec :: Spec
+specSpec = describe "AnySchema" $ do
+
+  context "ToJSON instance" $ do
+    let (Object jspec)        = toJSON $ jsonSchema (Proxy :: Proxy Person)
+        (Just (Object props)) = HashMap.lookup "properties" jspec
+        (Just (Array reqs))   = HashMap.lookup "required" jspec
+        (Just (Object ageO))  = HashMap.lookup "age" props
+        (Just (Object nameO)) = HashMap.lookup "name" props
+
+    it "lists required properties " $ do
+      toList reqs `shouldContain` [String "name"]
+      toList reqs `shouldContain` [String "age"]
+
+    it "contains the outermost type" $ do
+      HashMap.lookup "type" jspec `shouldBe` Just (String "object")
+
+    it "contains the nested types" $ do
+      HashMap.lookup "type" nameO `shouldBe` Just (String "string")
+      HashMap.lookup "type" ageO  `shouldBe` Just (String "number")
+
+    it "contains the nested constraints" $ do
+      HashMap.lookup "minLength" nameO `shouldBe` Just (Number 1)
+      HashMap.lookup "maxLength" nameO `shouldBe` Just (Number 100)
+      HashMap.lookup "minimum" ageO `shouldBe` Just (Number 0)
+      HashMap.lookup "maximum" ageO `shouldBe` Just (Number 200)
+
+
+
+type EvenInt = Validated (MultipleOf 2) Int
+
+type Name  = Validated (MinLength 1 :&& MaxLength 100) String
+type Age   = Validated (Minimum 0 :&& Maximum 200) Integer
+
+data Person = Person
+    { name :: Name
+    , age  :: Age
+    } deriving (Eq, Show, Read, Generic, ToJSON)
+
+instance JsonSchema Person where
+    type JsonType Person = ObjectSchema
+    jsonSchema' _ = mempty { properties = Map.fromList
+                                [ ("name", (Required, jsonSchema namep))
+                                , ("age" , (Required, jsonSchema agep ))
+                                ]
+                          }
+      where namep = Proxy :: Proxy Name
+            agep  = Proxy :: Proxy Age
diff --git a/verdict-json.cabal b/verdict-json.cabal
new file mode 100644
--- /dev/null
+++ b/verdict-json.cabal
@@ -0,0 +1,80 @@
+name:                verdict-json
+version:             0.0.0.0
+synopsis:            JSON instances and JSON Schema for verdict
+description:
+  DO NOT USE! Unstable, not thoroughly tested.
+license:             BSD3
+license-file:        LICENSE
+author:              Julian K. Arni
+maintainer:          jkarni@gmail.com
+copyright:           (c) Julian K. Arni
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     Verdict.JSON
+  ghc-options:         -Wall
+  other-modules:       Verdict.JSON.Class
+                     , Verdict.JSON.Types
+  default-extensions:  DeriveFunctor
+                     , DeriveGeneric
+                     , DeriveDataTypeable
+                     , TypeOperators
+                     , MultiParamTypeClasses
+                     , DataKinds
+                     , FunctionalDependencies
+                     , PolyKinds
+                     , ScopedTypeVariables
+                     , FlexibleInstances
+                     , FlexibleContexts
+                     , TypeFamilies
+  build-depends:       base >=4.8 && <4.9
+                     , aeson
+                     , verdict == 0.0.*
+                     , containers
+                     , text
+                     , unordered-containers
+                     , vector
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+
+test-suite spec
+  type:                exitcode-stdio-1.0
+  ghc-options:         -Wall
+  default-language:    Haskell2010
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  other-modules:       Verdict.JSONSpec
+  default-extensions:  DeriveAnyClass
+                     , DeriveFunctor
+                     , DeriveGeneric
+                     , TypeOperators
+                     , MultiParamTypeClasses
+                     , DataKinds
+                     , FunctionalDependencies
+                     , PolyKinds
+                     , ScopedTypeVariables
+                     , FlexibleInstances
+                     , FlexibleContexts
+                     , TypeFamilies
+  build-depends:       base == 4.*
+                     , aeson >= 0.10
+                     , containers
+                     , unordered-containers
+                     , vector
+                     , verdict
+                     , verdict-json
+                     , hspec == 2.*
+
+executable Example
+  buildable:           False
+  main-is:             Example.hs
+  hs-source-dirs:      examples
+  ghc-options:         -Wall
+  build-depends:       base >=4.7 && <4.9
+                     , verdict
+                     , verdict-json
+                     , containers
+                     , aeson
+  default-language:    Haskell2010
