diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# unjson-0.15.4 (2022-02-26)
+* Add support for aeson >= 2.
+* Add support for GHC 9.0 and 9.2.
+* Drop support for GHC < 8.8.
+
 # unjson-0.15.3 (2020-05-05)
 * GHC 8.8 and 8.10 support
 
diff --git a/src/Data/Unjson.hs b/src/Data/Unjson.hs
--- a/src/Data/Unjson.hs
+++ b/src/Data/Unjson.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE CPP #-}
-
 -- | @Unjson@: bidirectional JSON (de)serialization with strong error
 -- reporting capabilities and automatic documentation generation.
 --
@@ -84,75 +83,87 @@
 -- 'unjsonToJSON', 'unjsonToByteStringLazy',
 -- 'unjsonToByteStringBuilder' and 'render'.
 module Data.Unjson
-(
--- * Serialization to JSON
-  unjsonToJSON
-, unjsonToJSON'
-, unjsonToByteStringLazy
-, unjsonToByteStringLazy'
-, unjsonToByteStringBuilder
-, unjsonToByteStringBuilder'
-, unjsonToByteStringBuilder''
-, Options(..)
+  ( -- * Serialization to JSON
+    unjsonToJSON
+  , unjsonToJSON'
+  , unjsonToByteStringLazy
+  , unjsonToByteStringLazy'
+  , unjsonToByteStringBuilder
+  , unjsonToByteStringBuilder'
+  , unjsonToByteStringBuilder''
+  , Options(..)
 
--- * Data definitions
-, Unjson(..)
-, UnjsonDef(..)
+  -- * Data definitions
+  , Unjson(..)
+  , UnjsonDef(..)
 
--- ** Objects
-, objectOf
-, field
-, fieldBy
-, fieldOpt
-, fieldOptBy
-, fieldDef
-, fieldDefBy
-, fieldReadonly
-, fieldReadonlyBy
-, FieldDef(..)
--- ** Arrays
-, arrayOf
-, arrayWithModeOf
-, arrayWithModeOf'
-, arrayWithPrimaryKeyOf
-, arrayWithModeAndPrimaryKeyOf
-, ArrayMode(..)
--- ** Maps, enums, sums
-, mapOf
-, enumOf
-, enumUnjsonDef
-, disjointUnionOf
-, unionOf
--- ** Helpers
-, unjsonAeson
-, unjsonAesonWithDoc
+  -- ** Objects
+  , objectOf
+  , field
+  , fieldBy
+  , fieldOpt
+  , fieldOptBy
+  , fieldDef
+  , fieldDefBy
+  , fieldReadonly
+  , fieldReadonlyBy
+  , FieldDef(..)
+  -- ** Arrays
+  , arrayOf
+  , arrayWithModeOf
+  , arrayWithModeOf'
+  , arrayWithPrimaryKeyOf
+  , arrayWithModeAndPrimaryKeyOf
+  , ArrayMode(..)
+  -- ** Maps, enums, sums
+  , mapOf
+  , enumOf
+  , enumUnjsonDef
+  , disjointUnionOf
+  , unionOf
+  -- ** Helpers
+  , unjsonAeson
+  , unjsonAesonWithDoc
 
--- * Documentation rendering
-, render
-, renderForPath
-, renderDoc
-, renderDocForPath
+  -- * Documentation rendering
+  , render
+  , renderForPath
+  , renderDoc
+  , renderDocForPath
 
--- * Parsing and updating
-, parse
-, update
-, Result(..)
-, Anchored(..)
-, Problem
-, Problems
-, Path(..)
-, PathElem(..)
+  -- * Parsing and updating
+  , parse
+  , update
+  , Result(..)
+  , Anchored(..)
+  , Problem
+  , Problems
+  , Path(..)
+  , PathElem(..)
 
-, unjsonInvmapR
-, unjsonIsConstrByName
-, unjsonIPv4AsWord32
-)
-where
+  , unjsonInvmapR
+  , unjsonIsConstrByName
+  , unjsonIPv4AsWord32
+  ) where
 
+import Control.Applicative.Free
+import Control.Exception
+import Control.Monad
+import Data.Data
+import Data.Fixed
+import Data.Functor.Invariant
+import Data.Hashable
+import Data.Maybe
+import Data.Monoid hiding (Ap)
+import Data.Primitive.Types
+import Data.Scientific
+import Data.Time.Clock
+import Data.Time.LocalTime
+import Foreign.Storable
 import qualified Data.Aeson as Aeson
 import qualified Data.Aeson.Types as Aeson
+import qualified Data.ByteString.Builder as Builder
 import qualified Data.ByteString.Lazy as BSL
-import qualified Data.ByteString.Lazy.Builder as Builder
 import qualified Data.HashSet as HashSet
 import qualified Data.IntMap as IntMap
 import qualified Data.IntSet as IntSet
@@ -166,36 +177,21 @@
 import qualified Data.Vector.Primitive
 import qualified Data.Vector.Storable
 import qualified Data.Vector.Unboxed
-import Data.Typeable
-import Data.Data
-import Data.Maybe
-import Data.Monoid hiding (Ap)
-import Data.Primitive.Types
-import Data.Hashable
-import Data.Scientific
-import Data.Time.LocalTime
-import Data.Time.Clock
-import Data.Fixed
-import Foreign.Storable
-import Control.Applicative.Free
-import Data.Functor.Invariant
-import qualified Data.HashMap.Strict as HashMap
-import qualified Data.HashMap.Lazy as LazyHashMap
-import Control.Exception
 
-import Control.Monad hiding (fail)
-import Control.Monad.Fail (MonadFail (..))
 import Data.Bits
-import Data.Word
+import Data.Char
 import Data.Int
-import Data.Ratio
 import Data.List
+import Data.Ratio
+import Data.Word
+import qualified Data.Unjson.Internal.Aeson.Compat as AC
 import qualified Text.ParserCombinators.ReadP as ReadP
-import Data.Char
-import Prelude hiding (fail)
-
 import qualified Text.PrettyPrint.HughesPJ as P
 
+#if !MIN_VERSION_aeson(2,0,1)
+import qualified Data.HashMap.Strict as HashMap
+#endif
+
 -- | Describe a path from root JSON element to a specific
 -- position. JSON has only two types of containers: objects and
 -- arrays, so there are only two types of keys needed to index into
@@ -379,27 +375,30 @@
 mapFst f (a,b) = (f a, b)
 
 instance (Typeable v, Unjson v) => Unjson (Map.Map String v)
-  where unjsonDef = invmap (Map.fromList . map (mapFst Text.unpack) . HashMap.toList)
-                                     (HashMap.fromList . map (mapFst Text.pack) . Map.toList)
+  where unjsonDef = invmap (Map.fromList . map (mapFst AC.toString) . AC.toList)
+                                     (AC.fromList . map (mapFst AC.fromString) . Map.toList)
                                      unjsonDef
 instance (Typeable v, Unjson v) => Unjson (Map.Map Text.Text v)
-  where unjsonDef = invmap (Map.fromList . HashMap.toList)
-                                     (HashMap.fromList . Map.toList)
+  where unjsonDef = invmap (Map.fromList . fmap (mapFst AC.toText) . AC.toList)
+                                     (AC.fromList . fmap (mapFst AC.fromText) . Map.toList)
                                      unjsonDef
 instance (Typeable v, Unjson v) => Unjson (Map.Map LazyText.Text v)
-  where unjsonDef = invmap (Map.fromList . map (mapFst LazyText.fromStrict) . HashMap.toList)
-                                     (HashMap.fromList . map (mapFst LazyText.toStrict) . Map.toList)
+  where unjsonDef = invmap (Map.fromList . map (mapFst (LazyText.fromStrict . AC.toText)) . AC.toList)
+                                     (AC.fromList . map (mapFst (AC.fromText . LazyText.toStrict)) . Map.toList)
                                      unjsonDef
+instance (Typeable v, Unjson v) => Unjson (AC.KeyMap v)
+  where unjsonDef = MapUnjsonDef unjsonDef pure id
+
+#if !MIN_VERSION_aeson(2,0,1)
 instance (Typeable v, Unjson v) => Unjson (HashMap.HashMap String v)
   where unjsonDef = invmap (HashMap.fromList . map (mapFst Text.unpack) . HashMap.toList)
                                      (HashMap.fromList . map (mapFst Text.pack) . HashMap.toList)
                                      unjsonDef
-instance (Typeable v, Unjson v) => Unjson (HashMap.HashMap Text.Text v)
-  where unjsonDef = MapUnjsonDef unjsonDef pure id
 instance (Typeable v, Unjson v) => Unjson (HashMap.HashMap LazyText.Text v)
   where unjsonDef = invmap (HashMap.fromList . map (mapFst LazyText.fromStrict) . HashMap.toList)
                                      (HashMap.fromList . map (mapFst LazyText.toStrict) . HashMap.toList)
                                      unjsonDef
+#endif
 
 instance (Unjson a,Unjson b) => Unjson (a,b) where
   unjsonDef = TupleUnjsonDef $ fmap pure
@@ -590,7 +589,7 @@
   TupleUnjsonDef    :: Ap (TupleFieldDef k) (Result k) -> UnjsonDef k
   DisjointUnjsonDef :: Text.Text -> [(Text.Text, k -> Bool, Ap (FieldDef k) (Result k))] -> UnjsonDef k
   UnionUnjsonDef    :: [(k -> Bool, Ap (FieldDef k) (Result k))] -> UnjsonDef k
-  MapUnjsonDef      :: Typeable k => UnjsonDef k -> (HashMap.HashMap Text.Text k -> Result v) -> (v -> HashMap.HashMap Text.Text k) -> UnjsonDef v
+  MapUnjsonDef      :: Typeable k => UnjsonDef k -> (AC.KeyMap k -> Result v) -> (v -> AC.KeyMap k) -> UnjsonDef v
 
 instance Invariant UnjsonDef where
   invmap f g (SimpleUnjsonDef name p s) = SimpleUnjsonDef name (fmap f . p) (s . g)
@@ -690,15 +689,15 @@
     (ArrayModeParseAndOutputSingle,[b]) -> unjsonToJSON' opt f b
     (_,c) -> Aeson.toJSON (map (unjsonToJSON' opt f) c)
 unjsonToJSON' opt (ObjectUnjsonDef f) a =
-  Aeson.object (objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)
+  Aeson.object $ AC.convertPair <$> (objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)
 unjsonToJSON' opt (TupleUnjsonDef f) a =
   Aeson.toJSON (tupleDefToArray (unjsonToJSON' opt) a f)
 unjsonToJSON' opt (DisjointUnjsonDef k l) a =
-  Aeson.object ((k,Aeson.toJSON nm) : objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)
+  Aeson.object $ AC.convertPair <$> ((k,Aeson.toJSON nm) : objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)
   where
     [(nm,_,f)] = filter (\(_,is,_) -> is a) l
 unjsonToJSON' opt (UnionUnjsonDef l) a =
-  Aeson.object (objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)
+  Aeson.object $ AC.convertPair <$> (objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)
   where
     [(_,f)] = filter (\(is,_) -> is a) l
 unjsonToJSON' opt (MapUnjsonDef f _ g) a =
@@ -794,7 +793,7 @@
 unjsonToByteStringBuilder'' level opt (MapUnjsonDef f _ g) a =
   unjsonGroup level opt (Builder.char8 '{') (Builder.char8 '}') serx obj
   where
-    obj = LazyHashMap.toList (fmap (unjsonToByteStringBuilder'' (level + indent opt) opt f) (g a))
+    obj = AC.toList (fmap (unjsonToByteStringBuilder'' (level + indent opt) opt f) (g a))
     serx (key,val) = Builder.lazyByteString (Aeson.encode (Aeson.toJSON key)) <> Builder.char8 ':'
                      <> (if pretty opt then Builder.char8 ' ' else mempty) <> val
 
@@ -881,7 +880,7 @@
 
 parseUpdating (DisjointUnjsonDef k l) ov v
   = case Aeson.parseEither Aeson.parseJSON v of
-      Right v' -> case HashMap.lookup k v' of
+      Right v' -> case AC.lookup (AC.fromText k) v' of
         Just x -> case Aeson.parseEither Aeson.parseJSON x of
           Right xx -> case filter (\(nm,_,_) -> nm==xx) l of
             [(_,_,f)] -> join (runAp (lookupByFieldDef v' ov) f)
@@ -896,7 +895,7 @@
         fail e
 parseUpdating (UnionUnjsonDef l) ov v
   = case Aeson.parseEither Aeson.parseJSON v of
-      Right v' -> case filter (\(_,f) -> isJust (mapM_ (\k -> HashMap.lookup k v') (listRequiredKeys f))) l of
+      Right v' -> case filter (\(_,f) -> isJust (mapM_ (\k -> AC.lookup (AC.fromText k) v') (listRequiredKeys f))) l of
         ((_,f):_) -> join (runAp (lookupByFieldDef v' ov) f)
         _ -> fail $ "union value type could not be recognized based on presence of keys"
       Left e ->
@@ -905,7 +904,7 @@
   = case Aeson.parseEither Aeson.parseJSON v of
       Right v' ->
         let hov = fmap h ov in
-        join $ fmap g $ HashMap.traverseWithKey (\k1 v1 -> resultPrependKey k1 $ parseUpdating f (join (fmap (HashMap.lookup k1) hov)) v1) v'
+        join $ fmap g $ AC.traverseWithKey (\k1 v1 -> resultPrependKey (AC.toText k1) $ parseUpdating f (join (fmap (AC.lookup k1) hov)) v1) v'
       Left e ->
         fail e
 
@@ -973,20 +972,20 @@
 
 lookupByFieldDef :: Aeson.Object -> Maybe s -> FieldDef s a -> Result a
 lookupByFieldDef v ov (FieldReqDef name _docstring f valuedef)
-  = resultPrependKey name $ case HashMap.lookup name v of
+  = resultPrependKey name $ case AC.lookup (AC.fromText name) v of
       Just x  -> parseUpdating valuedef (fmap f ov) x
       Nothing -> case ov of
                    Just xov -> Result (f xov) []
                    Nothing -> fail "missing key"
 lookupByFieldDef v ov (FieldDefDef name _docstring def f valuedef)
-  = resultPrependKey name $ case HashMap.lookup name v of
+  = resultPrependKey name $ case AC.lookup (AC.fromText name) v of
       Just Aeson.Null -> Result def []
       Just x  -> parseUpdating valuedef (fmap f ov) x
       Nothing -> case ov of
                    Just xov -> Result (f xov) []
                    Nothing -> Result def []
 lookupByFieldDef v ov (FieldOptDef name _docstring f valuedef)
-  = resultPrependKey name $ case HashMap.lookup name v of
+  = resultPrependKey name $ case AC.lookup (AC.fromText name) v of
       Just Aeson.Null -> Result Nothing []
       Just x  -> case ov of
                    Just xov -> fmap Just (parseUpdating valuedef (f xov) x)
@@ -1168,7 +1167,7 @@
 -- > objectOf $ pure X
 -- >   <*> field "xmap" xMap
 -- >       "Map string to Y value"
-mapOf :: Typeable x => UnjsonDef x -> UnjsonDef (LazyHashMap.HashMap Text.Text x)
+mapOf :: Typeable x => UnjsonDef x -> UnjsonDef (AC.LazyKeyMap x)
 mapOf def = MapUnjsonDef def pure id
 
 -- | Provide sum type support. Bidirectional case matching in Haskell
diff --git a/src/Data/Unjson/Internal/Aeson/Compat.hs b/src/Data/Unjson/Internal/Aeson/Compat.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Unjson/Internal/Aeson/Compat.hs
@@ -0,0 +1,77 @@
+{-# LANGUAGE CPP #-}
+module Data.Unjson.Internal.Aeson.Compat
+  ( module Map
+#if !MIN_VERSION_aeson(2,0,1)
+  , KeyMap
+#endif
+  , fromText
+  , toText
+  , convertPair
+  , LazyKeyMap
+  , testErrorMessage
+  , fromString
+  , toString
+  , lazyKeyMapFromList
+  ) where
+
+import Data.Text
+import qualified Data.Aeson.Types as A
+
+#if MIN_VERSION_aeson(2,0,1)
+
+import Data.Aeson.KeyMap as Map
+import qualified Data.Aeson.Key as K
+
+fromText :: Text -> K.Key
+fromText = K.fromText
+
+toText :: K.Key -> Text
+toText = K.toText
+
+fromString :: String -> K.Key
+fromString = K.fromString
+
+toString :: K.Key -> String
+toString = K.toString
+
+convertPair :: (Text, A.Value) -> A.Pair
+convertPair (t, v) = (K.fromText t, v)
+
+type LazyKeyMap = KeyMap
+
+testErrorMessage :: Text
+testErrorMessage = "Error in $: parsing KeyMap failed, expected Object, but encountered String"
+
+lazyKeyMapFromList :: [(K.Key, v)] -> Map.KeyMap v
+lazyKeyMapFromList = Map.fromList
+
+#else
+
+import Data.HashMap.Strict as Map
+import Data.Hashable (Hashable)
+import qualified Data.HashMap.Lazy as LazyHashMap
+
+type KeyMap a = Map.HashMap Text a
+type LazyKeyMap a = LazyHashMap.HashMap Text a
+
+fromText :: Text -> Text
+fromText = id
+
+toText :: Text -> Text
+toText = id
+
+fromString :: String -> String
+fromString = id
+
+toString :: String -> String
+toString = id
+
+convertPair :: (Text, A.Value) -> (Text, A.Value)
+convertPair = id
+
+testErrorMessage :: Text
+testErrorMessage = "Error in $: parsing HashMap ~Text failed, expected Object, but encountered String"
+
+lazyKeyMapFromList :: (Eq k, Hashable k) => [(k, v)] -> HashMap k v
+lazyKeyMapFromList = LazyHashMap.fromList
+#endif
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -2,24 +2,20 @@
 {-# OPTIONS_GHC -fno-warn-type-defaults #-}
 module Main where
 
-import Prelude hiding (fail)
-import qualified Data.Text as Text
-import qualified Data.ByteString.Lazy.Char8 as BSL
-import Data.Int
-import Data.Typeable
-import Data.Unjson
-import qualified Data.Aeson as Aeson
 import Data.Aeson ((.=))
-import Data.List
 import Data.Data
 import Data.Functor.Invariant
-import Control.Monad.Fail (MonadFail(fail))
+import Data.Int
+import Data.List
+import Data.Unjson
+import System.Exit (ExitCode (..), exitWith)
 import Test.HUnit
-import qualified Data.HashMap.Strict as HashMap
-import qualified Data.HashMap.Lazy as LazyHashMap
+import qualified Data.Aeson as Aeson
+import qualified Data.ByteString.Lazy.Char8 as BSL
 import qualified Data.Map as Map
+import qualified Data.Text as Text
 
-import System.Exit (ExitCode (..), exitWith)
+import qualified Data.Unjson.Internal.Aeson.Compat as AC
 
 default (Text.Text, String, Int, Double)
 
@@ -181,8 +177,10 @@
     (Anchored (Path [ PathElemKey "port" ])
     "parsing Integer failed, expected Number, but encountered Object") (iss!!1)
   assertEqual "Credentials must be object error info is present"
-                (Anchored (Path [ PathElemKey "credentials"
-                                ]) "Error in $: parsing HashMap ~Text failed, expected Object, but encountered String") (iss!!2)
+                (Anchored 
+                  (Path [ PathElemKey "credentials" ]) 
+                  AC.testErrorMessage
+                ) (iss!!2)
   return ()
 
 test_tuple_parsing :: Test
@@ -661,7 +659,7 @@
          <*> field "a_map"
              id
              "The only map"
-  let unjsonMapByExplicit :: (Unjson a, Typeable a) => UnjsonDef (HashMap.HashMap Text.Text a)
+  let unjsonMapByExplicit :: (Unjson a, Typeable a) => UnjsonDef (AC.KeyMap a)
       unjsonMapByExplicit = objectOf $ pure id
          <*> fieldBy "a_map"
              id
@@ -672,10 +670,10 @@
   assertEqual "Parsing keeps proper order in Data.Map" (Map.fromList [("k1"::String, 12::Int),("k2", 1122), ("a4", 666)]) val0
   let Result val1 iss1 = parse unjsonMapByInstance jsonEmbedded
   assertEqual "No problems" [] iss1
-  assertEqual "Parsing keeps proper order" (HashMap.fromList [("k1"::String, 12::Int),("k2", 1122), ("a4", 666)]) val1
+  assertEqual "Parsing keeps proper order" (AC.fromList [(AC.fromString ("k1" :: String), 12::Int),("k2", 1122), ("a4", 666)]) val1
   let Result val2 iss2 = parse unjsonMapByExplicit jsonEmbedded
   assertEqual "No problems" [] iss2
-  assertEqual "Parsing keeps proper order" (LazyHashMap.fromList [("k1"::Text.Text, 12::Int),("k2", 1122), ("a4", 666)]) val2
+  assertEqual "Parsing keeps proper order" (AC.lazyKeyMapFromList [(AC.fromText ("k1"::Text.Text), 12::Int),("k2", 1122), ("a4", 666)]) val2
   return ()
 
 
diff --git a/unjson.cabal b/unjson.cabal
--- a/unjson.cabal
+++ b/unjson.cabal
@@ -1,6 +1,6 @@
 cabal-version:       1.18
 name:                unjson
-version:             0.15.3
+version:             0.15.4
 synopsis:            Bidirectional JSON parsing and generation.
 description:         Bidirectional JSON parsing and generation
                      with automatic documentation support.
@@ -15,7 +15,7 @@
 category:            Data
 build-type:          Simple
 extra-source-files:  README.md, CHANGELOG.md
-tested-with:         GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.3 || ==8.10.1
+tested-with:         GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.1
 
 source-repository head
   type:     git
@@ -28,10 +28,10 @@
 
 library
   exposed-modules:     Data.Unjson
-  -- other-modules:
+                       Data.Unjson.Internal.Aeson.Compat
   -- other-extensions:
-  build-depends:       base >= 4.9 && < 5,
-                       aeson >= 1.0 && < 1.5,
+  build-depends:       base >= 4.13 && < 5,
+                       aeson >= 1.0,
                        attoparsec,
                        bytestring >= 0.10,
                        containers,
