diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for vformat-aeson
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2020 Version Cloud
+
+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 Jorah Gao 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# vformat-aeson
+
+Please see http://hackage.haskell.org/package/vformat-aeson
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/src/Text/Format/Aeson.hs b/src/Text/Format/Aeson.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Format/Aeson.hs
@@ -0,0 +1,77 @@
+{-| Extend vformat to 'Aeson'
+
+The instance will try the following conditions one by one
+when formatting 'Value':
+
+(1) the arg is a 'Number', use 'formatInteger' or 'formatRealFloat' to format
+its value.
+
+(2) the arg is a 'String', use 'formatString' to format its value.
+
+(3) the key is an empty key (i.e. 'mempty'), encode the whole 'Value' into
+string, then use 'formatString' to format the string.
+
+(4) the arg is 'Array' and the topmost key is 'Index', get the element by the
+index, then format the element.
+
+(5) the arg is 'Object' and the topmost key is 'Name', get the value by the
+name, then format the value.
+
+(6) raise an 'ArgKeyError'.
+
+
+If you have a 'ToJSON' datatype, you can extend vformat to it directly,
+or just convert it into 'Value' (use 'toJSON') and then format the 'Value'.
+
+=== Example
+>>> :set -XDeriveGeneric
+>>> import GHC.Generics
+>>> data Color = Red | Yellow | Blue deriving Generic
+>>> instance ToJSON Color
+>>> data Flag = Flag Color Int Int deriving Generic
+>>> instance ToJSON Flag
+>>> data Country = Country { name :: String, flag :: Flag } deriving Generic
+>>> instance ToJSON Country
+>>> let country = toJSON $ Country "X" $ Flag Blue 100 50
+>>> format1 "{name} {flag!0} {flag!1} {flag!2}" country
+"X Blue 100 50"
+>>> format "{}" country
+"{\"flag\":[\"Blue\",100,50],\"name\":\"X\"}"
+-}
+module Text.Format.Aeson ( ) where
+
+
+import           Control.Exception
+import           Data.Aeson
+import qualified Data.ByteString.Lazy.Char8 as B
+import qualified Data.HashMap.Strict        as M
+import           Data.Scientific            hiding (formatScientific)
+import qualified Data.Text                  as T
+import           Data.Vector
+import           Text.Format
+
+
+instance FormatArg Value where
+  formatArg (Number x) k   = formatScientific (floatingOrInteger x) k
+  formatArg (String x) k   = formatString (T.unpack x) k
+  formatArg x k            | k == mempty = formatJSON x k
+  formatArg (Array xs) k   = formatArray (topKey k) xs (popKey k)
+  formatArg (Object obj) k = formatObject (topKey k) obj (popKey k)
+  formatArg _ _            = const $ Left $ toException ArgKeyError
+
+
+formatJSON :: ToJSON a => a -> Formatter
+formatJSON = formatString . B.unpack . encode
+
+
+formatScientific :: (RealFloat r, Integral i) => Either r i -> Formatter
+formatScientific (Left x)  = formatRealFloat x
+formatScientific (Right x) = formatInteger $ toInteger x
+
+
+formatArray :: ArgKey -> Array -> Formatter
+formatArray (Index i) xs = case (xs !? i) of (Just x) -> formatArg x
+
+formatObject :: ArgKey -> Object -> Formatter
+formatObject (Name k) obj =
+  case (M.lookup (T.pack k) obj) of (Just x) -> formatArg x
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,55 @@
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main ( main ) where
+
+import           Control.Exception
+import           Data.Aeson
+import           Data.ByteString.Lazy.Char8 (pack)
+import           Data.Maybe
+import           GHC.Generics
+import           Text.Format
+import           Text.Format.Aeson
+
+main :: IO ()
+main = do
+  putStrLn "\n<<<<<<<<<<<<<<<<<<<<<< Test <<<<<<<<<<<<<<<<<<<<<<"
+
+  it "format JSON Null" $ format "{}" Null == ("null" :: String)
+  it "format JSON Bool" $ format "{}" (toJSON True) == ("true" :: String)
+  it "format JSON String" $
+    format "{}" (toJSON ("hello world" :: String)) == ("hello world" :: String)
+  it "format JSON Integer Number" $
+    format "{}" (toJSON (123456 :: Int)) == ("123456" :: String)
+  it "format JSON Float Number" $
+    format "{:.4f}" (toJSON (123.456789 :: Float)) == ("123.4568" :: String)
+  it "format JSON Array" $
+    format1 "{} {} {}" (toJSON $ Flag Blue 100 50) == ("Blue 100 50" :: String)
+  it "format JSON Object" $
+    let country = toJSON $ Country "X" $ Flag Blue 100 50
+        result = "X Blue 100 50" :: String
+    in format1 "{name} {flag!0} {flag!1} {flag!2}" country == result
+  it "format whole JSON as string" $
+    let country = toJSON $ Country "X" $ Flag Blue 100 50
+    in isJust (decode (pack $ format "{}" country) :: Maybe Value)
+
+  putStrLn "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
+
+
+data Color = Red | Yellow | Blue deriving Generic
+
+instance ToJSON Color
+
+
+data Flag = Flag Color Int Int deriving Generic
+
+instance ToJSON Flag
+
+
+data Country = Country { name :: String, flag :: Flag } deriving Generic
+
+instance ToJSON Country
+
+
+it :: String -> Bool -> IO ()
+it = flip assert . putStrLn
diff --git a/vformat-aeson.cabal b/vformat-aeson.cabal
new file mode 100644
--- /dev/null
+++ b/vformat-aeson.cabal
@@ -0,0 +1,66 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.1.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 04a48365fa4cecc564b45118baa50969724d5aebda9279d064cac1b73c4bef67
+
+name:           vformat-aeson
+version:        0.1.0.0
+synopsis:       Use vformat format Aeson
+description:    Please see http://hackage.haskell.org/package/vformat-aeson
+category:       Text, Format, JSON
+homepage:       https://github.com/versioncloud/vformat-aeson#readme
+bug-reports:    https://github.com/versioncloud/vformat-aeson/issues
+author:         Jorah Gao
+maintainer:     jorah@version.cloud
+copyright:      Copyright (c) 2020 Version Cloud
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/versioncloud/vformat-aeson
+
+library
+  exposed-modules:
+      Text.Format.Aeson
+  other-modules:
+      Paths_vformat_aeson
+  hs-source-dirs:
+      src
+  build-depends:
+      aeson >=1.2 && <2.0
+    , base >=4.7 && <5
+    , bytestring >=0.10 && <1.0
+    , scientific >=0.3 && <1.0
+    , text >=1.2 && <2.0
+    , unordered-containers >=0.2 && <1.0
+    , vector >=0.12 && <1.0
+    , vformat >=0.12 && <1.0
+  default-language: Haskell2010
+
+test-suite vformat-aeson-test
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  other-modules:
+      Paths_vformat_aeson
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N -O0
+  build-depends:
+      aeson >=1.2 && <2.0
+    , base >=4.7 && <5
+    , bytestring >=0.10 && <1.0
+    , scientific >=0.3 && <1.0
+    , text >=1.2 && <2.0
+    , unordered-containers >=0.2 && <1.0
+    , vector >=0.12 && <1.0
+    , vformat >=0.12 && <1.0
+    , vformat-aeson
+  default-language: Haskell2010
