diff --git a/Data/Aeson/Quick.hs b/Data/Aeson/Quick.hs
new file mode 100644
--- /dev/null
+++ b/Data/Aeson/Quick.hs
@@ -0,0 +1,184 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+module Data.Aeson.Quick
+    (
+    -- $use
+    -- $examples
+      module Ae
+    , (.?)
+    , (.!)
+    , extract
+    , (.%)
+    , build
+    , Structure(..)
+    , parseStructure
+    ) where
+
+
+import Control.Applicative
+import Control.Monad
+import Control.DeepSeq
+
+import Data.Aeson as Ae
+import qualified Data.Aeson.Types as AT
+import Data.Attoparsec.Text hiding (parse)
+import Data.Char
+import qualified Data.HashMap.Strict as H
+import Data.Monoid
+import Data.String
+import qualified Data.Text as T
+import qualified Data.Vector as V
+
+import GHC.Generics (Generic)
+
+
+data Structure = Obj [(T.Text, Bool, Structure)]
+               | Arr Structure
+               | Val
+ deriving (Eq, Ord, Generic)
+
+instance NFData Structure
+
+instance IsString Structure where
+  fromString s =
+    let e = error $ "Invalid structure: " ++ s
+     in either (\_ -> e) id $ parseStructure $ T.pack s
+
+
+instance Show Structure where
+  show (Val) = "Val"
+  show (Arr s) = "[" ++ show s ++ "]"
+  show (Obj xs) = "{" ++ drop 1 (concatMap go xs) ++ "}"
+    where go (k,o,s) = "," ++ T.unpack k ++ (if o then "?" else "")
+                           ++ (if s == Val then "" else ":" ++ show s)
+    
+
+-- | Parse a structure, can fail
+parseStructure :: T.Text -> Either String Structure
+parseStructure = parseOnly structure
+  where
+    structure = object' <|> array
+    object' = Obj <$> ("{" *> sepBy1 lookups (char ',') <* "}")
+    array = Arr <$> ("[" *> structure <* "]")
+    lookups = (,,) <$> (takeWhile1 isKeyChar)
+                   <*> ("?" *> pure True <|> pure False)
+                   <*> (":" *> structure <|> pure Val)
+    isKeyChar = isAlphaNum -- TODO
+
+
+{- |
+Extracts instances of 'FromJSON' from a 'Value'
+
+This is a wrapper around 'extract' which does the actual work.
+
+Examples assume 'FromJSON' Foo and 'FromJSON' Bar.
+
+Extract key from object:
+
+>>> "{key}" .? value :: Maybe Foo
+
+Extract list of objects:
+
+>>> "[{key}]" .? value :: Maybe [Foo]
+
+Extract with optional key:
+
+>>> "{key,opt?}" .? value :: Maybe (Foo, Maybe Bar)
+-}
+(.?) :: FromJSON a => Value -> Structure -> Maybe a
+(.?) = AT.parseMaybe . flip extract
+{-# INLINE (.?) #-}
+
+-- TODO: Appropriate infixes?
+
+{- |
+The (very!) unsafe version of '.?'. This can fail very easily, do not depend on this in your program. Will probably be removed.
+-}
+(.!) :: FromJSON a => Value -> Structure -> a
+(.!) v s = either err id $ AT.parseEither (extract s) v
+  where err msg = error $ show s ++ ": " ++ msg ++ " in " ++ show v
+{-# INLINE (.!) #-}
+
+
+{- |
+The 'Parser' that executes a 'Structure' against a 'Value' to return an instance of 'FromJSON'. 
+-}
+extract :: FromJSON a => Structure -> Value -> AT.Parser a
+extract structure = go structure >=> parseJSON
+  where
+    go (Obj [s])  = withObject "" (flip look s)
+    go (Obj sx)   = withObject "" (forM sx . look) >=> pure . toJSON
+    go (Arr s)    = withArray  "" (V.mapM $ go s)   >=> pure . Array
+    go Val = pure
+    look v (k,False,Val) = v .: k
+    look v (k,False,s)   = v .:  k >>= go s
+    look v (k,True,s)    = v .:? k >>= maybe (pure Null) (go s)
+
+
+{- |
+Turns data into JSON objects. 
+
+This is a wrapper around 'build' which does the actual work.
+
+Build a simple Value:
+
+>>> encode $ "{a}" .% True
+{\"a\": True}
+
+Build a complex Value:
+
+>>> encode $ "[{a}]" '.%' [True, False]
+"[{\"a\":true},{\"a\":false}]"
+-}
+(.%) :: ToJSON a => Structure -> a -> Value
+(.%) s = build s Null
+{-# INLINE (.%) #-}
+
+
+{- |
+Executes a 'Structure' against provided data to update a 'Value'.
+
+Note: /Still has undefined behaviours/, not at all stable.
+-}
+build :: ToJSON a => Structure -> Value -> a -> Value
+build structure val = go structure val . toJSON
+  where
+    go (Val)      _          r         = r
+    go (Arr s)    (Array v)  (Array r) = Array $ V.zipWith (go s) v r 
+    go (Arr s)    Null       (Array r) = Array $ V.map (go s Null) r
+    go (Arr s)    Null       r         = toJSON [go s Null r]
+    go (Obj [ks]) (Object v) r         = Object $ update v ks r
+    go (Obj keys) Null       r         = go (Obj keys) (Object mempty) r
+    go (Obj ks)   (Object v) (Array r) = Object $
+      let maps = zip ks (V.toList r)
+       in foldl (\v' (s,r') -> update v' s r') v maps
+    go (Obj keys) (Object v) r         = r
+    go a b c = error $ show (a,b,c) -- TODO
+    update v (k,_,s) r =
+      let startVal = go s (H.lookupDefault Null k v) r
+       in H.insert k startVal v
+
+
+-- $use
+--
+-- aeson-quick is a library for terse marshalling of data to and from aeson's
+-- 'Value'.
+--
+-- It works on the observation that by turning objects into tuples inside
+-- the 'Value', the type system can be employed to do more of the work.
+--
+-- For example, given the JSON:
+--
+-- > { "name": "bob"
+-- > , "age": 29
+-- > , "hobbies": [{"name": "tennis"}, {"name": "cooking"}]
+-- > }
+--
+-- You can write: 
+--
+-- @
+-- extractHobbyist :: 'Value' -> 'Maybe' ('String', 'Int', ['String'])
+-- extractHobbyist value = value '.?' "{name,age,hobbies:[{name}]}"
+-- @
+--
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2011, Scott Sadler.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the author nor the names of his contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE 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 AUTHORS 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/aeson-quick.cabal b/aeson-quick.cabal
new file mode 100644
--- /dev/null
+++ b/aeson-quick.cabal
@@ -0,0 +1,60 @@
+Name:              aeson-quick
+Version:           0.1.1
+Build-Type:        Simple
+Cabal-Version:     >= 1.10
+License:           BSD3
+License-File:      LICENSE
+Author:            Scott Sadler
+Maintainer:        Scott Sadler <scott@scottsadler.de>
+Homepage:          https://github.com/libscott/aeson-quick
+Category:          Text, Web, JSON
+Synopsis:          Quick JSON extractions with Aeson
+Description:       DSL on top of Aeson. This library is /experimental/.
+Copyright:         (c) 2014-2017 Scott Sadler
+Stability:         Experimental
+Tested-With:       GHC == 7.8.4, GHC == 7.10.2, GHC == 8.0.1
+
+
+Library
+  Exposed-modules:    Data.Aeson.Quick
+  hs-source-dirs:     .
+  Build-Depends:      base                  >= 4      && <= 4.9.0.0
+                    , aeson                 >= 0.8    && <  0.12
+                    , attoparsec            >= 0.12   && <  0.14
+                    , deepseq               >= 1.3    && <  1.5
+                    , text                  >= 1.2    && <  1.3
+                    , unordered-containers  >= 0.2.5  && <  0.3
+                    , vector                >= 0.10   && <  0.12
+  default-language: Haskell2010
+
+test-suite aeson-quick-test
+  type:               exitcode-stdio-1.0
+  hs-source-dirs:     test
+  main-is:            Test.hs
+  build-depends:      aeson-quick
+                    , base                  >= 4      && <= 4.9.0.0
+                    , bytestring            >= 0.10.5 && <  0.11
+                    , aeson                 >= 0.8    && <  0.12
+                    , microlens             >= 0.4    && <  0.5
+                    , text                  >= 1.2    && <  1.3
+                    , attoparsec            >= 0.12   && <  0.14
+                    , tasty                 >= 0.10   && <  0.12
+                    , tasty-hunit           >= 0.9    && <  0.10
+  default-language: Haskell2010
+
+benchmark benchmark
+  type:               exitcode-stdio-1.0
+  main-is:            Benchmark.hs
+  hs-source-dirs:     test
+  ghc-options:        -Wall -rtsopts -threaded -with-rtsopts=-N
+  build-depends:      aeson-quick
+                    , base                  >= 4      && <= 4.9.0.0
+                    , aeson                 >= 0.8    && <  0.12
+                    , bytestring            >= 0.10   && <  0.11
+                    , criterion             >= 0.1    && <  1.2
+                    , text                  >= 1.2    && <  1.3
+  default-language: Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/libscott/aeson-quick
diff --git a/test/Benchmark.hs b/test/Benchmark.hs
new file mode 100644
--- /dev/null
+++ b/test/Benchmark.hs
@@ -0,0 +1,72 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Control.Applicative
+import Control.Monad
+
+import Criterion.Main
+
+import Data.Aeson.Quick
+import Data.Aeson.Types (parseMaybe)
+import qualified Data.ByteString.Lazy as BL
+import Data.Text (Text)
+
+import System.IO.Unsafe
+
+main :: IO ()
+main = defaultMain
+  -- TODO: Refactor such that "bench" also tests
+  [ bench "aqGetSimple"        $ nf getSimple jsonSimple
+  , bench "aesonGetSimple"     $ nf aesonGetSimple jsonSimple
+  , bench "aqGetComplex"       $ nf getComplex jsonComplex
+  , bench "aesonGetComplex"    $ nf aesonGetComplex jsonComplex
+  , bench "aqSetSimple"        $ nf setSimple simple
+  , bench "aesonSetSimple"     $ nf aesonSetSimple simple
+  , bench "aqSetComplex"       $ nf setComplex complex
+  , bench "aesonSetComplex"    $ nf aesonSetComplex complex
+  , bench "parseSimple"        $ nf parseStructure "{a}"
+  , bench "parseComplex"       $ nf parseStructure "{a,b:[{c,d:[{e,f}]}]}"
+ ]
+  where
+    jsonSimple = d "{\"a\":2,\"b\":[1,1]}" :: Value
+    jsonComplex = d $ unsafePerformIO $ BL.readFile "test/complex.json"
+
+    check :: (Value -> Maybe a) -> Value -> a
+    check f = maybe (error "Nothing") id . f
+
+    getSimple, aesonGetSimple :: Value -> Integer
+    getSimple = check (.? "{a}")
+    aesonGetSimple = check $ parseMaybe $ withObject "" (.: "a")
+    
+    getComplex, aesonGetComplex :: Value -> [(Text,Float,[Text],[Text])]
+    getComplex = check (.! "[{id,ppu,batters:{batter:[{id}]},topping:[{id}]}]")
+    aesonGetComplex = check $ parseMaybe $ parseJSON >=> mapM (\o ->
+      (,,,) <$> o .: "id" <*> o .: "ppu" <*> batters o <*> toppings o)
+      where
+        batters = (.:"batters") >=> withObject "" (.:"batter")
+                                >=> mapM (withObject "" (.:"id"))
+        toppings = (.:"topping") >=> mapM (withObject "" (.:"id"))
+    
+    simple = object ["a" .= Number 1]
+    setSimple, aesonSetSimple :: Value -> Bool
+    setSimple r = r `must` build "{a}" Null (1::Int) 
+    aesonSetSimple r = r `must` object ["a" .= (1::Int)]
+
+    Just complex = decode "{\"a\":1,\"b\":[{\"a\":1},{\"a\":2},{\"a\":3}]}"
+    setComplex, aesonSetComplex :: Value -> Bool
+    setComplex r =
+      let vals = ((1,[1,2,3])::(Int,[Int]))
+       in r `must` build "{a,b:[{a}]}" Null vals
+    aesonSetComplex r =
+      let inner = [object ["a" .= n] | n <- [1,2,3::Int]]
+       in r `must` object ["a" .= (1::Int), "b" .= inner]
+
+
+must :: Eq a => a -> a -> Bool
+must a b = if a == b then True else error "False"
+
+d :: BL.ByteString -> Value
+d s = case decode s of
+           Just v -> v
+           Nothing -> error $ "Coult not decode JSON: " ++ show s
diff --git a/test/Test.hs b/test/Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Test.hs
@@ -0,0 +1,162 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+
+import Control.Applicative
+
+import Data.Aeson.Quick
+import Data.ByteString.Lazy (ByteString)
+import Data.Either
+
+import Lens.Micro
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+main :: IO ()
+main = defaultMain $ testGroup "Tests" 
+  [ oneKey
+  , multipleKeys
+  , deepKey
+  , keyInArray
+  , complex
+  , optionalKey
+  , nonExistentKey
+  , asLens
+  , parseInvalid
+  , showStructure
+  ]
+
+
+oneKey :: TestTree
+oneKey = testGroup "oneKey"
+  [ testCase "get" $
+      val .! "{a}" @?= one
+
+  , testCase "set" $
+      "{a}" .% Null `jt` "{\"a\":null}"
+  ]
+  where val = d "{\"a\":1}"
+
+
+multipleKeys :: TestTree
+multipleKeys = testGroup "multipleKeys"
+  [ testCase "get" $
+      multiple .! "{a,b}" @?= (one,two)
+
+  , testCase "set" $
+      build "{a,b}" multiple (two,[one,one]) `jt` "{\"a\":2,\"b\":[1,1]}"
+  ]
+  where multiple = d "{\"a\":1,\"b\":2}"
+
+
+deepKey :: TestTree
+deepKey = testGroup "deepKey"
+  [ testCase "get" $
+      multilevel .! "{a:{b}}" @?= Just one
+
+  , testCase "set" $
+      build "{a:{b}}" multilevel two `jt` "{\"a\":{\"b\":2}}"
+  ]
+  where multilevel = d "{\"a\":{\"b\":1}}"
+
+
+keyInArray :: TestTree
+keyInArray = testGroup "keyInArray"
+  [ testCase "get" $
+      many .! "[{a}]" @?= [one,two]
+
+  , testCase "set" $
+      build "[{a}]" many [True,False] `jt` "[{\"a\":true},{\"a\":false}]"
+  ]
+  where many = d "[{\"a\":1},{\"a\":2}]"
+
+
+complex :: TestTree
+complex = testGroup "complex"
+  [ testCase "get" $
+      val .! "{a,b:[{a}]}" @?= (one,[[two,one]])
+  , testCase "set" $
+      build "{a,b:[{a}]}" val (two,[[one]]) `jt` "{\"a\":2,\"b\":[{\"a\":[1]}]}"
+  ]
+  where val = d "{\"a\":1,\"b\":[{\"a\":[2,1]}]}"
+
+
+optionalKey :: TestTree
+optionalKey = testGroup "optionalKey"
+  [ testCase "get" $
+      val .! "{a?,b?}" @?= (Just one, Nothing :: Maybe Int)
+  ]
+  where val = d "{\"a\":1}"
+
+
+nonExistentKey :: TestTree
+nonExistentKey = testGroup "nonExistentKey"
+  [ testCase "get" $
+      val .? "{b}" @?= (Nothing :: Maybe Int)
+  
+  , testCase "set" $
+      build "{a}" (d "{}") Null `jt` "{\"a\":null}"
+
+  , testCase "setDeep" $
+      "{a:[{b}]}" .% Null `jt` "{\"a\":[{\"b\":null}]}"
+
+  , testCase "setDeepArray" $
+      "[{a}]" .% [one,two] `jt` "[{\"a\":1},{\"a\":2}]"
+
+  , testCase "setDeepMany" $
+      let v = (1,[(10,11)]) :: (Int,[(Int,Int)])
+       in "{my,complex:[{data,structure}]}" .% v
+            @?= d "{\"my\":1,\"complex\":[{\"data\":10,\"structure\":11}]}" 
+  ]
+  where val = d "{\"a\":1}"
+
+
+asLens :: TestTree
+asLens = testGroup "asLens"
+  [ testCase "get" $
+      let l = queLens "{a,b}" :: Lens' Value (Int,Int)
+       in val ^. l . _2 @?= two
+
+  , testCase "getDoesNotExist" $
+      -- doesn't work, make a Traversal?
+      --d "{}" ^? queLens "{a}" @?= (Nothing :: Maybe (Int,Int))
+      putStr "SKIPPED " >> pure ()
+
+  , testCase "set" $
+      (val & (queLens "{a,b}") .~ (two,one)) `jt` "{\"a\":2,\"b\":1}"
+  ]
+  where
+    val = d "{\"a\":1,\"b\":2}"
+    queLens :: (FromJSON a, ToJSON a) => Structure -> Lens' Value a
+    queLens s = lens (.!s) (build s)
+
+
+parseInvalid :: TestTree
+parseInvalid = testGroup "parseInvalid"
+  [ testCase "noKey" $
+      isLeft (parseStructure "{a,{b}}") @?= True
+  ]
+
+
+showStructure :: TestTree
+showStructure = testGroup "showStructure"
+  [ testCase "all" $
+      show ("[{a:[{b?,c}]}]"::Structure) @?= "[{a:[{b?,c}]}]"
+  ]
+
+
+one, two :: Int
+one = 1 
+two = 2
+
+
+jt :: Value -> ByteString -> IO ()
+jt v b = encode v @?= b
+
+
+d :: ByteString -> Value
+d s = case decode s of
+           Just v -> v
+           Nothing -> error $ "Coult not decode JSON: " ++ show s
+
