diff --git a/Data/Aeson.hs b/Data/Aeson.hs
--- a/Data/Aeson.hs
+++ b/Data/Aeson.hs
@@ -149,7 +149,7 @@
 import Data.Aeson.Encoding (encodingToLazyByteString)
 import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith, eitherDecodeWith, eitherDecodeStrictWith, jsonEOF, json, jsonEOF', json')
 import Data.Aeson.Types
-import Data.Aeson.Types.Internal (JSONPath, formatError, (<?>))
+import Data.Aeson.Types.Internal (formatError)
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as L
 
@@ -257,7 +257,7 @@
 -- | Like 'decodeFileStrict' but returns an error message when decoding fails.
 eitherDecodeFileStrict :: (FromJSON a) => FilePath -> IO (Either String a)
 eitherDecodeFileStrict =
-  fmap (eitherFormatError . eitherDecodeStrictWith jsonEOF ifromJSON) . B.readFile
+  fmap eitherDecodeStrict . B.readFile
 {-# INLINE eitherDecodeFileStrict #-}
 
 -- | Like 'decode'' but returns an error message when decoding fails.
@@ -274,7 +274,7 @@
 -- | Like 'decodeFileStrict'' but returns an error message when decoding fails.
 eitherDecodeFileStrict' :: (FromJSON a) => FilePath -> IO (Either String a)
 eitherDecodeFileStrict' =
-  fmap (eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON) . B.readFile
+  fmap eitherDecodeStrict' . B.readFile
 {-# INLINE eitherDecodeFileStrict' #-}
 
 -- $use
diff --git a/Data/Aeson/Compat.hs b/Data/Aeson/Compat.hs
deleted file mode 100644
--- a/Data/Aeson/Compat.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# LANGUAGE CPP #-}
-
-module Data.Aeson.Compat
-  (
-    fromStrict
-  ) where
-
-import qualified Data.ByteString      as S
-import qualified Data.ByteString.Lazy as L
-
-fromStrict :: S.ByteString -> L.ByteString
-#if MIN_VERSION_bytestring(0, 9, 2)
-fromStrict = L.fromChunks . (:[])
-#else
-fromStrict = L.fromStrict
-#endif
diff --git a/Data/Aeson/Encoding/Builder.hs b/Data/Aeson/Encoding/Builder.hs
--- a/Data/Aeson/Encoding/Builder.hs
+++ b/Data/Aeson/Encoding/Builder.hs
@@ -44,7 +44,6 @@
 import Data.ByteString.Builder.Prim as BP
 import Data.ByteString.Builder.Scientific (scientificBuilder)
 import Data.Char (chr, ord)
-import Data.Semigroup ((<>))
 import Data.Scientific (Scientific, base10Exponent, coefficient)
 import Data.Text.Encoding (encodeUtf8BuilderEscaped)
 import Data.Time (UTCTime(..))
diff --git a/Data/Aeson/Encoding/Internal.hs b/Data/Aeson/Encoding/Internal.hs
--- a/Data/Aeson/Encoding/Internal.hs
+++ b/Data/Aeson/Encoding/Internal.hs
@@ -63,7 +63,6 @@
 import Data.ByteString.Builder (Builder, char7, toLazyByteString)
 import Data.Int
 import Data.Scientific (Scientific)
-import Data.Semigroup (Semigroup ((<>)))
 import Data.Text (Text)
 import Data.Time (Day, LocalTime, TimeOfDay, UTCTime, ZonedTime)
 import Data.Typeable (Typeable)
diff --git a/Data/Aeson/Parser/Internal.hs b/Data/Aeson/Parser/Internal.hs
--- a/Data/Aeson/Parser/Internal.hs
+++ b/Data/Aeson/Parser/Internal.hs
@@ -2,12 +2,9 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE OverloadedStrings #-}
-#if MIN_VERSION_ghc_prim(0,3,1)
-{-# LANGUAGE MagicHash #-}
-#endif
 #if __GLASGOW_HASKELL__ <= 710 && __GLASGOW_HASKELL__ >= 706
 -- Work around a compiler bug
-{-# OPTIONS_GHC -fsimpl-tick-factor=200 #-}
+{-# OPTIONS_GHC -fsimpl-tick-factor=300 #-}
 #endif
 -- |
 -- Module:      Data.Aeson.Parser.Internal
@@ -58,8 +55,10 @@
 import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
 import Data.Function (fix)
 import Data.Functor.Compat (($>))
+import Data.Bits (testBit)
 import Data.Scientific (Scientific)
 import Data.Text (Text)
+import qualified Data.Text.Encoding as TE
 import Data.Vector (Vector)
 import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse)
 import qualified Data.Attoparsec.ByteString as A
@@ -67,16 +66,13 @@
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Unsafe as B
 import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString.Lazy as BSL
+import qualified Data.ByteString.Lazy.Char8 as C
+import qualified Data.ByteString.Builder as B
 import qualified Data.HashMap.Strict as H
 import qualified Data.Scientific as Sci
 import Data.Aeson.Parser.Unescape (unescapeText)
 
-#if MIN_VERSION_ghc_prim(0,3,1)
-import GHC.Base (Int#, (==#), isTrue#, word2Int#, orI#, andI#)
-import GHC.Word (Word8(W8#))
-import qualified Data.Text.Encoding as TE
-#endif
-
 #define BACKSLASH 92
 #define CLOSE_CURLY 125
 #define CLOSE_SQUARE 93
@@ -323,37 +319,20 @@
 -- | Parse a string without a leading quote.
 jstring_ :: Parser Text
 {-# INLINE jstring_ #-}
-jstring_ = {-# SCC "jstring_" #-} do
-#if MIN_VERSION_ghc_prim(0,3,1)
-  (s, S _ escaped) <- A.runScanner startState go <* A.anyWord8
-  -- We escape only if there are
-  -- non-ascii (over 7bit) characters or backslash present.
-  --
-  -- Note: if/when text will have fast ascii -> text conversion
-  -- (e.g. uses utf8 encoding) we can have further speedup.
-  if isTrue# escaped
-    then case unescapeText s of
-      Right r  -> return r
-      Left err -> fail $ show err
-    else return (TE.decodeUtf8 s)
- where
-    startState              = S 0# 0#
-    go (S skip escaped) (W8# c)
-      | isTrue# skip        = Just (S 0# escaped')
-      | isTrue# (w ==# 34#) = Nothing   -- double quote
-      | otherwise           = Just (S skip' escaped')
-      where
-        w = word2Int# c
-        skip' = w ==# 92# -- backslash
-        escaped' = escaped
-            `orI#` (w `andI#` 0x80# ==# 0x80#) -- c >= 0x80
-            `orI#` skip'
-            `orI#` (w `andI#` 0x1f# ==# w)     -- c < 0x20
+jstring_ = do
+  s <- A.takeWhile (\w -> w /= DOUBLE_QUOTE && w /= BACKSLASH && not (testBit w 7))
+  let txt = TE.decodeUtf8 s
+  w <- A.peekWord8
+  case w of
+    Nothing -> fail "string without end"
+    Just DOUBLE_QUOTE -> A.anyWord8 $> txt
+    _ -> jstringSlow s
 
-data S = S Int# Int#
-#else
+jstringSlow :: B.ByteString -> Parser Text
+{-# INLINE jstringSlow #-}
+jstringSlow s' = {-# SCC "jstringSlow" #-} do
   s <- A.scan startState go <* A.anyWord8
-  case unescapeText s of
+  case unescapeText (B.append s' s) of
     Right r  -> return r
     Left err -> fail $ show err
  where
@@ -364,7 +343,6 @@
       | otherwise = let a' = c == backslash
                     in Just a'
       where backslash = BACKSLASH
-#endif
 
 decodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString -> Maybe a
 decodeWith p to s =
@@ -390,13 +368,34 @@
       L.Done _ v     -> case to v of
                           ISuccess a      -> Right a
                           IError path msg -> Left (path, msg)
-      L.Fail _ ctx msg -> Left ([], buildMsg ctx msg)
+      L.Fail notparsed ctx msg -> Left ([], buildMsg notparsed ctx msg)
   where
-    buildMsg :: [String] -> String -> String
-    buildMsg [] msg = msg
-    buildMsg (expectation:_) msg =
-      msg ++ ". Expecting " ++ expectation
+    buildMsg :: L.ByteString -> [String] -> String -> String
+    buildMsg notYetParsed [] msg = msg ++ formatErrorLine notYetParsed
+    buildMsg notYetParsed (expectation:_) msg =
+      msg ++ ". Expecting " ++ expectation ++ formatErrorLine notYetParsed
 {-# INLINE eitherDecodeWith #-}
+
+-- | Grab the first 100 bytes from the non parsed portion and
+-- format to get nicer error messages
+formatErrorLine :: L.ByteString -> String
+formatErrorLine bs =
+  C.unpack .
+  -- if formatting results in empty ByteString just return that
+  -- otherwise construct the error message with the bytestring builder
+  (\bs' ->
+     if BSL.null bs'
+       then BSL.empty
+       else
+         B.toLazyByteString $
+         B.stringUtf8 " at '" <> B.lazyByteString bs' <> B.stringUtf8 "'"
+  ) .
+  -- if newline is present cut at that position
+  BSL.takeWhile (10 /=) .
+  -- remove spaces, CR's, tabs, backslashes and quotes characters
+  BSL.filter (`notElem` [9, 13, 32, 34, 47, 92]) .
+  -- take 100 bytes
+  BSL.take 100 $ bs
 
 eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
                        -> Either (JSONPath, String) a
diff --git a/Data/Aeson/TH.hs b/Data/Aeson/TH.hs
--- a/Data/Aeson/TH.hs
+++ b/Data/Aeson/TH.hs
@@ -116,8 +116,11 @@
     , mkLiftParseJSON2
     ) where
 
-import Prelude.Compat
+import Prelude.Compat hiding (fail)
 
+-- We don't have MonadFail Q, so we should use `fail` from real `Prelude`
+import Prelude (fail)
+
 import Control.Applicative ((<|>))
 import Data.Aeson (Object, (.:), FromJSON(..), FromJSON1(..), FromJSON2(..), ToJSON(..), ToJSON1(..), ToJSON2(..))
 import Data.Aeson.Types (Options(..), Parser, SumEncoding(..), Value(..), defaultOptions, defaultTaggedObject)
@@ -135,22 +138,15 @@
 import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
 import qualified Data.Monoid as Monoid
 import Data.Set (Set)
-#if MIN_VERSION_template_haskell(2,8,0)
 import Language.Haskell.TH hiding (Arity)
-#else
-import Language.Haskell.TH
-#endif
 import Language.Haskell.TH.Datatype
-#if MIN_VERSION_template_haskell(2,7,0) && !(MIN_VERSION_template_haskell(2,8,0))
-import Language.Haskell.TH.Lib (starK)
-#endif
 #if MIN_VERSION_template_haskell(2,8,0) && !(MIN_VERSION_template_haskell(2,10,0))
 import Language.Haskell.TH.Syntax (mkNameG_tc)
 #endif
 import Text.Printf (printf)
 import qualified Data.Aeson.Encoding.Internal as E
 import qualified Data.Foldable as F (all)
-import qualified Data.HashMap.Strict as H (lookup, toList)
+import qualified Data.HashMap.Strict as H (difference, fromList, keys, lookup, toList)
 import qualified Data.List.NonEmpty as NE (length, reverse)
 import qualified Data.Map as M (fromList, keys, lookup , singleton, size)
 import qualified Data.Semigroup as Semigroup (Option(..))
@@ -929,12 +925,32 @@
             -> Name
             -> [Name]
             -> Name
+            -> Bool
             -> ExpQ
-parseRecord jc tvMap argTys opts tName conName fields obj =
+parseRecord jc tvMap argTys opts tName conName fields obj inTaggedObject =
+    (if rejectUnknownFields opts
+     then infixApp checkUnknownRecords [|(>>)|]
+     else id) $
     foldl' (\a b -> infixApp a [|(<*>)|] b)
            (infixApp (conE conName) [|(<$>)|] x)
            xs
     where
+      tagFieldNameAppender =
+          if inTaggedObject then (tagFieldName (sumEncoding opts) :) else id
+      knownFields = appE [|H.fromList|] $ listE $
+          map (\knownName -> tupE [appE [|T.pack|] $ litE $ stringL knownName, [|()|]]) $
+              tagFieldNameAppender $ map nameBase fields
+      checkUnknownRecords =
+          caseE (appE [|H.keys|] $ infixApp (varE obj) [|H.difference|] knownFields)
+              [ match (listP []) (normalB [|return ()|]) []
+              , newName "unknownFields" >>=
+                  \unknownFields -> match (varP unknownFields)
+                      (normalB $ appE [|fail|] $ infixApp
+                          (litE (stringL "Unknown fields: "))
+                          [|(++)|]
+                          (appE [|show|] (varE unknownFields)))
+                      []
+              ]
       x:xs = [ [|lookupField|]
                `appE` dispatchParseJSON jc conName tvMap argTy
                `appE` litE (stringL $ show tName)
@@ -1009,7 +1025,7 @@
                   , constructorFields  = argTys }
   (Left (_, obj)) = do
     argTys' <- mapM resolveTypeSynonyms argTys
-    parseRecord jc tvMap argTys' opts tName conName fields obj
+    parseRecord jc tvMap argTys' opts tName conName fields obj True
 parseArgs jc tvMap tName opts
   info@ConstructorInfo { constructorName    = conName
                        , constructorVariant = RecordConstructor fields
@@ -1024,7 +1040,7 @@
         argTys' <- mapM resolveTypeSynonyms argTys
         caseE (varE valName)
           [ match (conP 'Object [varP obj]) (normalB $
-              parseRecord jc tvMap argTys' opts tName conName fields obj) []
+              parseRecord jc tvMap argTys' opts tName conName fields obj False) []
           , matchFailed tName conName "Object"
           ]
 
@@ -1291,9 +1307,8 @@
         tyVarNames :: [Name]
         tyVarNames = M.keys tvMap
 
-    itf <- isTyFamily tyCon
-    if any (`mentionsName` tyVarNames) lhsArgs
-          || itf && any (`mentionsName` tyVarNames) tyArgs
+    itf <- isInTypeFamilyApp tyVarNames tyCon tyArgs
+    if any (`mentionsName` tyVarNames) lhsArgs || itf
        then outOfPlaceTyVarError jc conName
        else if any (`mentionsName` tyVarNames) rhsArgs
             then appsE $ varE (jsonFunValOrListName list jf $ toEnum numLastArgs)
@@ -1590,21 +1605,13 @@
 -- | Returns True if a Type has kind *.
 hasKindStar :: Type -> Bool
 hasKindStar VarT{}         = True
-#if MIN_VERSION_template_haskell(2,8,0)
 hasKindStar (SigT _ StarT) = True
-#else
-hasKindStar (SigT _ StarK) = True
-#endif
 hasKindStar _              = False
 
 -- Returns True is a kind is equal to *, or if it is a kind variable.
 isStarOrVar :: Kind -> Bool
-#if MIN_VERSION_template_haskell(2,8,0)
 isStarOrVar StarT  = True
 isStarOrVar VarT{} = True
-#else
-isStarOrVar StarK  = True
-#endif
 isStarOrVar _      = False
 
 -- Generate a list of fresh names with a common prefix, and numbered suffixes.
@@ -1652,21 +1659,44 @@
 isTyVar (SigT t _) = isTyVar t
 isTyVar _          = False
 
--- | Is the given type a type family constructor (and not a data family constructor)?
-isTyFamily :: Type -> Q Bool
-isTyFamily (ConT n) = do
-    info <- reify n
-    return $ case info of
+-- | Detect if a Name in a list of provided Names occurs as an argument to some
+-- type family. This makes an effort to exclude /oversaturated/ arguments to
+-- type families. For instance, if one declared the following type family:
+--
+-- @
+-- type family F a :: Type -> Type
+-- @
+--
+-- Then in the type @F a b@, we would consider @a@ to be an argument to @F@,
+-- but not @b@.
+isInTypeFamilyApp :: [Name] -> Type -> [Type] -> Q Bool
+isInTypeFamilyApp names tyFun tyArgs =
+  case tyFun of
+    ConT tcName -> go tcName
+    _           -> return False
+  where
+    go :: Name -> Q Bool
+    go tcName = do
+      info <- reify tcName
+      case info of
 #if MIN_VERSION_template_haskell(2,11,0)
-         FamilyI OpenTypeFamilyD{} _       -> True
+        FamilyI (OpenTypeFamilyD (TypeFamilyHead _ bndrs _ _)) _
+          -> withinFirstArgs bndrs
+        FamilyI (ClosedTypeFamilyD (TypeFamilyHead _ bndrs _ _) _) _
+          -> withinFirstArgs bndrs
 #else
-         FamilyI (FamilyD TypeFam _ _ _) _ -> True
-#endif
-#if MIN_VERSION_template_haskell(2,9,0)
-         FamilyI ClosedTypeFamilyD{} _     -> True
+        FamilyI (FamilyD TypeFam _ bndrs _) _
+          -> withinFirstArgs bndrs
+        FamilyI (ClosedTypeFamilyD _ bndrs _ _) _
+          -> withinFirstArgs bndrs
 #endif
-         _ -> False
-isTyFamily _ = return False
+        _ -> return False
+      where
+        withinFirstArgs :: [a] -> Q Bool
+        withinFirstArgs bndrs =
+          let firstArgs = take (length bndrs) tyArgs
+              argFVs    = freeVariables firstArgs
+          in return $ any (`elem` argFVs) names
 
 -- | Peel off a kind signature from a Type (if it has one).
 unSigT :: Type -> Type
@@ -1692,9 +1722,7 @@
     go :: Type -> [Name] -> Bool
     go (AppT t1 t2) names = go t1 names || go t2 names
     go (SigT t _k)  names = go t names
-#if MIN_VERSION_template_haskell(2,8,0)
                               || go _k names
-#endif
     go (VarT n)     names = n `elem` names
     go _            _     = False
 
@@ -1750,23 +1778,14 @@
 
 -- | Like uncurryType, except on a kind level.
 uncurryKind :: Kind -> NonEmpty Kind
-#if MIN_VERSION_template_haskell(2,8,0)
 uncurryKind = snd . uncurryTy
-#else
-uncurryKind (ArrowK k1 k2) = k1 <| uncurryKind k2
-uncurryKind k              = k :| []
-#endif
 
 createKindChain :: Int -> Kind
 createKindChain = go starK
   where
     go :: Kind -> Int -> Kind
     go k 0 = k
-#if MIN_VERSION_template_haskell(2,8,0)
     go k !n = go (AppT (AppT ArrowT StarT) k) (n - 1)
-#else
-    go k !n = go (ArrowK StarK k) (n - 1)
-#endif
 
 -- | Makes a string literal expression from a constructor's name.
 conNameExp :: Options -> ConstructorInfo -> Q Exp
@@ -1820,11 +1839,7 @@
 -------------------------------------------------------------------------------
 
 applySubstitutionKind :: Map Name Kind -> Type -> Type
-#if MIN_VERSION_template_haskell(2,8,0)
 applySubstitutionKind = applySubstitution
-#else
-applySubstitutionKind _ t = t
-#endif
 
 substNameWithKind :: Name -> Kind -> Type -> Type
 substNameWithKind n k = applySubstitutionKind (M.singleton n k)
@@ -1988,9 +2003,7 @@
 canRealizeKindStar :: Type -> StarKindStatus
 canRealizeKindStar t = case t of
     _ | hasKindStar t -> KindStar
-#if MIN_VERSION_template_haskell(2,8,0)
     SigT _ (VarT k) -> IsKindVar k
-#endif
     _ -> NotKindStar
 
 -- | Returns 'Just' the kind variable 'Name' of a 'StarKindStatus' if it exists.
diff --git a/Data/Aeson/Text.hs b/Data/Aeson/Text.hs
--- a/Data/Aeson/Text.hs
+++ b/Data/Aeson/Text.hs
@@ -26,7 +26,6 @@
 
 import Data.Aeson.Types (Value(..), ToJSON(..))
 import Data.Aeson.Encoding (encodingToLazyByteString)
-import Data.Semigroup ((<>))
 import Data.Scientific (FPFormat(..), Scientific, base10Exponent)
 import Data.Text.Lazy.Builder
 import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder)
diff --git a/Data/Aeson/Types.hs b/Data/Aeson/Types.hs
--- a/Data/Aeson/Types.hs
+++ b/Data/Aeson/Types.hs
@@ -35,6 +35,7 @@
     , parse
     , parseEither
     , parseMaybe
+    , parseFail
     , ToJSON(..)
     , KeyValue(..)
     , modifyFailure
@@ -124,6 +125,7 @@
     , sumEncoding
     , unwrapUnaryRecords
     , tagSingleConstructors
+    , rejectUnknownFields
 
     -- ** Options utilities
     , SumEncoding(..)
diff --git a/Data/Aeson/Types/FromJSON.hs b/Data/Aeson/Types/FromJSON.hs
--- a/Data/Aeson/Types/FromJSON.hs
+++ b/Data/Aeson/Types/FromJSON.hs
@@ -7,16 +7,14 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# LANGUAGE ViewPatterns #-}
 
-#if __GLASGOW_HASKELL__ >= 706
-{-# LANGUAGE PolyKinds #-}
-#endif
-
 #include "incoherent-compat.h"
 #include "overlapping-compat.h"
 
@@ -98,7 +96,6 @@
 import Data.Int (Int16, Int32, Int64, Int8)
 import Data.List.NonEmpty (NonEmpty(..))
 import Data.Maybe (fromMaybe)
-import Data.Semigroup ((<>))
 import Data.Proxy (Proxy(..))
 import Data.Ratio ((%), Ratio)
 import Data.Scientific (Scientific, base10Exponent)
@@ -120,9 +117,9 @@
 import Numeric.Natural (Natural)
 import Text.ParserCombinators.ReadP (readP_to_S)
 import Unsafe.Coerce (unsafeCoerce)
-import qualified Data.Aeson.Compat as Compat
 import qualified Data.Aeson.Parser.Time as Time
 import qualified Data.Attoparsec.ByteString.Char8 as A (endOfInput, parseOnly, scientific)
+import qualified Data.ByteString.Lazy as L
 import qualified Data.DList as DList
 import qualified Data.HashMap.Strict as H
 import qualified Data.HashSet as HashSet
@@ -157,18 +154,8 @@
 import qualified Data.Primitive.PrimArray as PM
 #endif
 
-#ifndef HAS_COERCIBLE
-#define HAS_COERCIBLE (__GLASGOW_HASKELL__ >= 707)
-#endif
 
-#if HAS_COERCIBLE
 import Data.Coerce (Coercible, coerce)
-coerce' :: Coercible a b => a -> b
-coerce' = coerce
-#else
-coerce' :: a -> b
-coerce' = unsafeCoerce
-#endif
 
 parseIndexedJSON :: (Value -> Parser a) -> Int -> Value -> Parser a
 parseIndexedJSON p idx value = p value <?> Index idx
@@ -440,11 +427,7 @@
 -- At the moment this type is intentionally not exported. 'FromJSONKeyFunction'
 -- can be inspected, but cannot be constructed.
 data CoerceText a where
-#if HAS_COERCIBLE
     CoerceText :: Coercible Text a => CoerceText a
-#else
-    CoerceText :: CoerceText a
-#endif
 
 -- | This type is related to 'ToJSONKeyFunction'. If 'FromJSONKeyValue' is used in the
 --   'FromJSONKey' instance, then 'ToJSONKeyValue' should be used in the 'ToJSONKey'
@@ -464,7 +447,7 @@
 
 -- | Only law abiding up to interpretation
 instance Functor FromJSONKeyFunction where
-    fmap h (FromJSONKeyCoerce CoerceText) = FromJSONKeyText (h . coerce')
+    fmap h (FromJSONKeyCoerce CoerceText) = FromJSONKeyText (h . coerce)
     fmap h (FromJSONKeyText f)            = FromJSONKeyText (h . f)
     fmap h (FromJSONKeyTextParser f)      = FromJSONKeyTextParser (fmap h . f)
     fmap h (FromJSONKeyValue f)           = FromJSONKeyValue (fmap h . f)
@@ -478,9 +461,7 @@
 --
 -- On pre GHC 7.8 this is unconstrainted function.
 fromJSONKeyCoerce ::
-#if HAS_COERCIBLE
     Coercible Text a =>
-#endif
     FromJSONKeyFunction a
 fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
 
@@ -488,29 +469,18 @@
 --
 -- See note on 'fromJSONKeyCoerce'.
 coerceFromJSONKeyFunction ::
-#if HAS_COERCIBLE
     Coercible a b =>
-#endif
     FromJSONKeyFunction a -> FromJSONKeyFunction b
-#if HAS_COERCIBLE
 coerceFromJSONKeyFunction = coerce
-#else
-coerceFromJSONKeyFunction (FromJSONKeyCoerce CoerceText) = FromJSONKeyCoerce CoerceText
-coerceFromJSONKeyFunction (FromJSONKeyText f)            = FromJSONKeyText (coerce' . f)
-coerceFromJSONKeyFunction (FromJSONKeyTextParser f)      = FromJSONKeyTextParser (fmap coerce' . f)
-coerceFromJSONKeyFunction (FromJSONKeyValue f)           = FromJSONKeyValue (fmap coerce' . f)
-#endif
 
 {-# RULES
   "FromJSONKeyCoerce: fmap id"     forall (x :: FromJSONKeyFunction a).
                                    fmap id x = x
   #-}
-#if HAS_COERCIBLE
 {-# RULES
   "FromJSONKeyCoerce: fmap coerce" forall x .
                                    fmap coerce x = coerceFromJSONKeyFunction x
   #-}
-#endif
 
 -- | Same as 'fmap'. Provided for the consistency with 'ToJSONKeyFunction'.
 mapFromJSONKeyFunction :: (a -> b) -> FromJSONKeyFunction a -> FromJSONKeyFunction b
@@ -797,13 +767,13 @@
 -- | A variant of 'withBoundedScientific_' parameterized by a function to apply
 -- to the 'Parser' in case of failure.
 withBoundedScientific_ :: (Parser a -> Parser a) -> (Scientific -> Parser a) -> Value -> Parser a
-withBoundedScientific_ whenFail f v@(Number scientific) =
-    if exponent > 1024
+withBoundedScientific_ whenFail f (Number scientific) =
+    if exp10 > 1024
     then whenFail (fail msg)
     else f scientific
   where
-    exponent = base10Exponent scientific
-    msg = "found a number with exponent " ++ show exponent ++ ", but it must not be greater than 1024"
+    exp10 = base10Exponent scientific
+    msg = "found a number with exponent " ++ show exp10 ++ ", but it must not be greater than 1024"
 withBoundedScientific_ whenFail _ v =
     whenFail (typeMismatch "Number" v)
 {-# INLINE withBoundedScientific_ #-}
@@ -823,7 +793,7 @@
 -- | Decode a nested JSON-encoded string.
 withEmbeddedJSON :: String -> (Value -> Parser a) -> Value -> Parser a
 withEmbeddedJSON _ innerParser (String txt) =
-    either fail innerParser $ eitherDecode (Compat.fromStrict $ T.encodeUtf8 txt)
+    either fail innerParser $ eitherDecode (L.fromStrict $ T.encodeUtf8 txt)
     where
         eitherDecode = eitherFormatError . eitherDecodeWith jsonEOF ifromJSON
         eitherFormatError = either (Left . uncurry formatError) Right
@@ -1230,9 +1200,9 @@
         :: String :* ConName :* TypeName :* Options :* FromArgs arity a
         -> Object -> Tagged isRecord (Parser (f a))
 
-instance (RecordFromJSON arity f) => FromTaggedObject' arity f True where
+instance (RecordFromJSON arity f, FieldNames f) => FromTaggedObject' arity f True where
     -- Records are unpacked in the tagged object
-    parseFromTaggedObject' (_ :* p) = Tagged . recordParseJSON p
+    parseFromTaggedObject' (_ :* p) = Tagged . recordParseJSON (True :* p)
 
 instance (ConsFromJSON arity f) => FromTaggedObject' arity f False where
     -- Nonnullary nonrecords are encoded in the contents field
@@ -1273,11 +1243,11 @@
          ) => ConsFromJSON' arity (S1 s a) True where
     consParseJSON' p@(cname :* tname :* opts :* fargs)
         | unwrapUnaryRecords opts = Tagged . fmap M1 . gParseJSON opts fargs
-        | otherwise = Tagged . withObject (showCons cname tname) (recordParseJSON p)
+        | otherwise = Tagged . withObject (showCons cname tname) (recordParseJSON (False :* p))
 
 instance RecordFromJSON arity f => ConsFromJSON' arity f True where
     consParseJSON' p@(cname :* tname :* _) =
-        Tagged . withObject (showCons cname tname) (recordParseJSON p)
+        Tagged . withObject (showCons cname tname) (recordParseJSON (False :* p))
 
 instance OVERLAPPING_
          ConsFromJSON' arity U1 False where
@@ -1303,21 +1273,55 @@
 
 --------------------------------------------------------------------------------
 
+class FieldNames f where
+    fieldNames :: f a -> [Text] -> [Text]
+
+instance (FieldNames a, FieldNames b) => FieldNames (a :*: b) where
+    fieldNames _ =
+      fieldNames (undefined :: a x) .
+      fieldNames (undefined :: b y)
+
+instance (Selector s) => FieldNames (S1 s f) where
+    fieldNames _ = (pack (selName (undefined :: M1 _i s _f _p)) :)
+
 class RecordFromJSON arity f where
     recordParseJSON
+        :: Bool :* ConName :* TypeName :* Options :* FromArgs arity a
+        -> Object -> Parser (f a)
+
+instance ( FieldNames f
+         , RecordFromJSON' arity f
+         ) => RecordFromJSON arity f where
+    recordParseJSON (fromTaggedSum :* p@(cname :* tname :* opts :* _)) =
+        \obj -> checkUnknown obj >> recordParseJSON' p obj
+        where
+            knownFields :: H.HashMap Text ()
+            knownFields = H.fromList $ map (,()) $
+                fieldNames (undefined :: f a)
+                [pack (tagFieldName (sumEncoding opts)) | fromTaggedSum]
+            checkUnknown =
+                if not (rejectUnknownFields opts)
+                then \_ -> return ()
+                else \obj -> case H.keys (H.difference obj knownFields) of
+                    [] -> return ()
+                    unknownFields -> contextCons cname tname $
+                        fail ("unknown fields: " ++ show unknownFields)
+
+class RecordFromJSON' arity f where
+    recordParseJSON'
         :: ConName :* TypeName :* Options :* FromArgs arity a
         -> Object -> Parser (f a)
 
-instance ( RecordFromJSON arity a
-         , RecordFromJSON arity b
-         ) => RecordFromJSON arity (a :*: b) where
-    recordParseJSON p obj =
-        (:*:) <$> recordParseJSON p obj
-              <*> recordParseJSON p obj
+instance ( RecordFromJSON' arity a
+         , RecordFromJSON' arity b
+         ) => RecordFromJSON' arity (a :*: b) where
+    recordParseJSON' p obj =
+        (:*:) <$> recordParseJSON' p obj
+              <*> recordParseJSON' p obj
 
 instance OVERLAPPABLE_ (Selector s, GFromJSON arity a) =>
-         RecordFromJSON arity (S1 s a) where
-    recordParseJSON (cname :* tname :* opts :* fargs) obj = do
+         RecordFromJSON' arity (S1 s a) where
+    recordParseJSON' (cname :* tname :* opts :* fargs) obj = do
         fv <- contextCons cname tname (obj .: label)
         M1 <$> gParseJSON opts fargs fv <?> Key label
       where
@@ -1325,16 +1329,16 @@
         sname = selName (undefined :: M1 _i s _f _p)
 
 instance INCOHERENT_ (Selector s, FromJSON a) =>
-         RecordFromJSON arity (S1 s (K1 i (Maybe a))) where
-    recordParseJSON (_ :* _ :* opts :* _) obj = M1 . K1 <$> obj .:? pack label
+         RecordFromJSON' arity (S1 s (K1 i (Maybe a))) where
+    recordParseJSON' (_ :* _ :* opts :* _) obj = M1 . K1 <$> obj .:? pack label
       where
         label = fieldLabelModifier opts sname
         sname = selName (undefined :: M1 _i s _f _p)
 
 -- Parse an Option like a Maybe.
 instance INCOHERENT_ (Selector s, FromJSON a) =>
-         RecordFromJSON arity (S1 s (K1 i (Semigroup.Option a))) where
-    recordParseJSON p obj = wrap <$> recordParseJSON p obj
+         RecordFromJSON' arity (S1 s (K1 i (Semigroup.Option a))) where
+    recordParseJSON' p obj = wrap <$> recordParseJSON' p obj
       where
         wrap :: S1 s (K1 i (Maybe a)) p -> S1 s (K1 i (Semigroup.Option a)) p
         wrap (M1 (K1 a)) = M1 (K1 (Semigroup.Option a))
@@ -1571,12 +1575,22 @@
         _           -> Scientific.toRealFloat <$> parseScientificText t
 
 instance (FromJSON a, Integral a) => FromJSON (Ratio a) where
-    parseJSON = withObject "Rational" $ \obj -> do
-        numerator <- obj .: "numerator"
-        denominator <- obj .: "denominator"
-        if denominator == 0
-        then fail "Ratio denominator was 0"
-        else pure $ numerator % denominator
+    parseJSON (Number x)
+      | exp10 <= 1024
+      , exp10 >= -1024 = return $! realToFrac x
+      | otherwise      = prependContext "Ratio" $ fail msg
+      where
+        exp10 = base10Exponent x
+        msg = "found a number with exponent " ++ show exp10
+           ++ ", but it must not be greater than 1024 or less than -1024"
+    parseJSON o = objParser o
+      where
+        objParser = withObject "Rational" $ \obj -> do
+            numerator <- obj .: "numerator"
+            denominator <- obj .: "denominator"
+            if denominator == 0
+            then fail "Ratio denominator was 0"
+            else pure $ numerator % denominator
     {-# INLINE parseJSON #-}
 
 -- | This instance includes a bounds check to prevent maliciously
@@ -1991,7 +2005,6 @@
 -- primitive
 -------------------------------------------------------------------------------
 
-#if MIN_VERSION_base(4,7,0)
 instance FromJSON a => FromJSON (PM.Array a) where
   -- note: we could do better than this if vector exposed the data
   -- constructor in Data.Vector.
@@ -2007,7 +2020,6 @@
 #if !MIN_VERSION_primitive(0,7,0)
 instance (PM.PrimUnlifted a,FromJSON a) => FromJSON (PM.UnliftedArray a) where
   parseJSON = fmap Exts.fromList . parseJSON
-#endif
 #endif
 #endif
 
diff --git a/Data/Aeson/Types/Internal.hs b/Data/Aeson/Types/Internal.hs
--- a/Data/Aeson/Types/Internal.hs
+++ b/Data/Aeson/Types/Internal.hs
@@ -44,6 +44,7 @@
     , parse
     , parseEither
     , parseMaybe
+    , parseFail
     , modifyFailure
     , prependFailure
     , parserThrowError
@@ -64,6 +65,7 @@
         , sumEncoding
         , unwrapUnaryRecords
         , tagSingleConstructors
+        , rejectUnknownFields
         )
 
     , SumEncoding(..)
@@ -93,7 +95,6 @@
 import Data.Hashable (Hashable(..))
 import Data.List (intercalate)
 import Data.Scientific (Scientific)
-import Data.Semigroup (Semigroup((<>)))
 import Data.String (IsString(..))
 import Data.Text (Text, pack, unpack)
 import Data.Time (UTCTime)
@@ -341,6 +342,10 @@
     mappend = (<>)
     {-# INLINE mappend #-}
 
+-- | Raise a parsing failure with some custom message.
+parseFail :: String -> Parser a
+parseFail = fail
+
 apP :: Parser (a -> b) -> Parser a -> Parser b
 apP d e = do
   b <- d
@@ -580,41 +585,53 @@
       -- the constructor tag. If 'False' the encoding will always
       -- follow the `sumEncoding`.
     , omitNothingFields :: Bool
-      -- ^ If 'True' record fields with a 'Nothing' value will be
-      -- omitted from the resulting object. If 'False' the resulting
+      -- ^ If 'True', record fields with a 'Nothing' value will be
+      -- omitted from the resulting object. If 'False', the resulting
       -- object will include those fields mapping to @null@.
       --
+      -- Note that this /does not/ affect parsing: 'Maybe' fields are
+      -- optional regardless of the value of 'omitNothingFields', subject
+      -- to the note below.
+      --
       -- === Note
       --
       -- Setting 'omitNothingFields' to 'True' only affects fields which are of
-      -- type 'Maybe' /uniformly/ in the 'ToJSON' or 'FromJSON' instance. In
-      -- particular, if the type of a field is declared as a type variable, it
+      -- type 'Maybe' /uniformly/ in the 'ToJSON' instance.
+      -- In particular, if the type of a field is declared as a type variable, it
       -- will not be omitted from the JSON object, unless the field is
       -- specialized upfront in the instance.
       --
+      -- The same holds for 'Maybe' fields being optional in the 'FromJSON' instance.
+      --
       -- ==== __Example__
       --
       -- The generic instance for the following type @Fruit@ depends on whether
       -- the instance head is @Fruit a@ or @Fruit (Maybe a)@.
       --
       -- @
-      -- data Fruit a =
+      -- data Fruit a = Fruit
       --   { apples :: a  -- A field whose type is a type variable.
       --   , oranges :: 'Maybe' Int
-      --   }
-      --
-      -- options :: 'Options'
-      -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }
+      --   } deriving 'Generic'
       --
       -- -- apples required, oranges optional
       -- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)).
-      -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a) where
-      --   'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options
+      -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a)
       --
       -- -- apples optional, oranges optional
       -- -- In this instance, the field apples is uniformly of type ('Maybe' a).
-      -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a)) where
-      --   'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options
+      -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a))
+      --
+      -- options :: 'Options'
+      -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }
+      --
+      -- -- apples always present in the output, oranges is omitted if 'Nothing'
+      -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit a) where
+      --   'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options
+      --
+      -- -- both apples and oranges are omitted if 'Nothing'
+      -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit ('Maybe' a)) where
+      --   'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options
       -- @
     , sumEncoding :: SumEncoding
       -- ^ Specifies how to encode constructors of a sum datatype.
@@ -624,10 +641,14 @@
     , tagSingleConstructors :: Bool
       -- ^ Encode types with a single constructor as sums,
       -- so that `allNullaryToStringTag` and `sumEncoding` apply.
+    , rejectUnknownFields :: Bool
+      -- ^ Applies only to 'Data.Aeson.FromJSON' instances. If a field appears in
+      -- the parsed object map, but does not appear in the target object, parsing
+      -- will fail, with an error message indicating which fields were unknown.
     }
 
 instance Show Options where
-  show (Options f c a o s u t) =
+  show (Options f c a o s u t r) =
        "Options {"
     ++ intercalate ", "
       [ "fieldLabelModifier =~ " ++ show (f "exampleField")
@@ -637,6 +658,7 @@
       , "sumEncoding = " ++ show s
       , "unwrapUnaryRecords = " ++ show u
       , "tagSingleConstructors = " ++ show t
+      , "rejectUnknownFields = " ++ show r
       ]
     ++ "}"
 
@@ -718,6 +740,7 @@
 -- , 'sumEncoding'             = 'defaultTaggedObject'
 -- , 'unwrapUnaryRecords'      = False
 -- , 'tagSingleConstructors'   = False
+-- , 'rejectUnknownFields'     = False
 -- }
 -- @
 defaultOptions :: Options
@@ -729,6 +752,7 @@
                  , sumEncoding             = defaultTaggedObject
                  , unwrapUnaryRecords      = False
                  , tagSingleConstructors   = False
+                 , rejectUnknownFields     = False
                  }
 
 -- | Default 'TaggedObject' 'SumEncoding' options:
diff --git a/Data/Aeson/Types/ToJSON.hs b/Data/Aeson/Types/ToJSON.hs
--- a/Data/Aeson/Types/ToJSON.hs
+++ b/Data/Aeson/Types/ToJSON.hs
@@ -8,15 +8,12 @@
 {-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE UndecidableInstances #-}
 
-#if __GLASGOW_HASKELL__ >= 706
-{-# LANGUAGE PolyKinds #-}
-#endif
-
 #include "overlapping-compat.h"
 #include "incoherent-compat.h"
 
@@ -82,7 +79,6 @@
 import Data.Int (Int16, Int32, Int64, Int8)
 import Data.List (intersperse)
 import Data.List.NonEmpty (NonEmpty(..))
-import Data.Semigroup ((<>))
 import Data.Proxy (Proxy(..))
 import Data.Ratio (Ratio, denominator, numerator)
 import Data.Scientific (Scientific)
@@ -103,7 +99,6 @@
 import Numeric.Natural (Natural)
 import qualified Data.Aeson.Encoding as E
 import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding)
-import qualified Data.ByteString as S
 import qualified Data.ByteString.Lazy as L
 import qualified Data.DList as DList
 import qualified Data.HashMap.Strict as H
@@ -144,14 +139,6 @@
 import qualified Data.Primitive.PrimArray as PM
 #endif
 
-#if !(MIN_VERSION_bytestring(0,10,0))
-import Foreign.ForeignPtr (withForeignPtr)
-import Foreign.Marshal.Utils (copyBytes)
-import Foreign.Ptr (plusPtr)
-import qualified Data.ByteString.Internal as S
-import qualified Data.ByteString.Lazy.Internal as L
-#endif
-
 toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value
 toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b)
 {-# INLINE toJSONPair #-}
@@ -515,7 +502,7 @@
     -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
     tot = T.dropAround (== '"')
         . T.decodeLatin1
-        . lazyToStrictByteString
+        . L.toStrict
         . E.encodingToLazyByteString
         . e
 
@@ -1982,7 +1969,6 @@
 -- primitive
 -------------------------------------------------------------------------------
 
-#if MIN_VERSION_base(4,7,0)
 instance ToJSON a => ToJSON (PM.Array a) where
   -- note: we could do better than this if vector exposed the data
   -- constructor in Data.Vector.
@@ -2004,7 +1990,6 @@
   toEncoding = toEncoding . Exts.toList
 #endif
 #endif
-#endif
 
 -------------------------------------------------------------------------------
 -- time
@@ -2054,7 +2039,7 @@
 stringEncoding = String
     . T.dropAround (== '"')
     . T.decodeLatin1
-    . lazyToStrictByteString
+    . L.toStrict
     . E.encodingToLazyByteString
 {-# INLINE stringEncoding #-}
 
@@ -2860,31 +2845,6 @@
     {-# INLINE toJSON #-}
     toEncoding = toEncoding2
     {-# INLINE toEncoding #-}
-
--------------------------------------------------------------------------------
--- pre-bytestring-0.10 compatibility
--------------------------------------------------------------------------------
-
-{-# INLINE lazyToStrictByteString #-}
-lazyToStrictByteString :: L.ByteString -> S.ByteString
-#if MIN_VERSION_bytestring(0,10,0)
-lazyToStrictByteString = L.toStrict
-#else
-lazyToStrictByteString = packChunks
-
--- packChunks is taken from the blaze-builder package.
-
--- | Pack the chunks of a lazy bytestring into a single strict bytestring.
-packChunks :: L.ByteString -> S.ByteString
-packChunks lbs =
-    S.unsafeCreate (fromIntegral $ L.length lbs) (copyChunks lbs)
-  where
-    copyChunks L.Empty                         _pf = return ()
-    copyChunks (L.Chunk (S.PS fpbuf o l) lbs') pf  = do
-        withForeignPtr fpbuf $ \pbuf ->
-            copyBytes pf (pbuf `plusPtr` o) l
-        copyChunks lbs' (pf `plusPtr` l)
-#endif
 
 --------------------------------------------------------------------------------
 
diff --git a/aeson.cabal b/aeson.cabal
--- a/aeson.cabal
+++ b/aeson.cabal
@@ -1,5 +1,5 @@
 name:            aeson
-version:         1.4.6.0
+version:         1.4.7.0
 license:         BSD3
 license-file:    LICENSE
 category:        Text, Web, JSON
@@ -88,7 +88,6 @@
     Data.Aeson.Encode
 
   other-modules:
-    Data.Aeson.Compat
     Data.Aeson.Encoding.Builder
     Data.Aeson.Internal.Functions
     Data.Aeson.Parser.Unescape
@@ -103,11 +102,12 @@
 
   -- GHC bundled libs
   build-depends:
-    base             >= 4.5.0.0 && < 5,
-    containers       >= 0.4.2.1 && < 0.7,
+    base             >= 4.7.0.0 && < 5,
+    bytestring       >= 0.10.4.0 && < 0.11,
+    containers       >= 0.5.5.1 && < 0.7,
     deepseq          >= 1.3.0.0 && < 1.5,
     ghc-prim         >= 0.2     && < 0.6,
-    template-haskell >= 2.7.0.0 && < 2.16,
+    template-haskell >= 2.9.0.0 && < 2.16,
     text             >= 1.2.3.0 && < 1.3,
     time             >= 1.4     && < 1.10
 
@@ -116,14 +116,8 @@
 
   -- Compat
   build-depends:
-    base-compat     >= 0.9.1    && < 0.12,
-    time-compat     >= 1.9.2.2  && < 1.10
-
-  if flag(bytestring-builder)
-    build-depends: bytestring >= 0.9.2.1 && < 0.10.4,
-                   bytestring-builder >= 0.10.4 && < 1
-  else
-    build-depends: bytestring >= 0.10.4 && < 0.11
+    base-compat-batteries >= 0.10.0   && < 0.12,
+    time-compat           >= 1.9.2.2  && < 1.10
 
   if !impl(ghc >= 8.6)
     build-depends:
@@ -227,7 +221,7 @@
     dlist,
     Diff >= 0.4 && < 0.5,
     filepath,
-    generic-deriving >= 1.10 && < 1.13,
+    generic-deriving >= 1.10 && < 1.14,
     ghc-prim >= 0.2,
     hashable >= 1.2.4.0,
     scientific,
diff --git a/attoparsec-iso8601/Data/Attoparsec/Time/Internal.hs b/attoparsec-iso8601/Data/Attoparsec/Time/Internal.hs
--- a/attoparsec-iso8601/Data/Attoparsec/Time/Internal.hs
+++ b/attoparsec-iso8601/Data/Attoparsec/Time/Internal.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE NoImplicitPrelude #-}
 -- |
 -- Module:      Data.Aeson.Internal.Time
@@ -19,50 +18,16 @@
 
 import Prelude.Compat
 
+import Data.Fixed (Fixed(MkFixed), Pico)
 import Data.Int (Int64)
-import Data.Time
-import Unsafe.Coerce (unsafeCoerce)
-
-#if MIN_VERSION_time(1,6,0)
-
-import Data.Time.Clock (diffTimeToPicoseconds)
-
-#endif
-
-#if MIN_VERSION_base(4,7,0)
-
-import Data.Fixed (Pico, Fixed(MkFixed))
-
-#else
-
-import Data.Fixed (Pico)
-
-#endif
-
-#if !MIN_VERSION_time(1,6,0)
-
-diffTimeToPicoseconds :: DiffTime -> Integer
-diffTimeToPicoseconds = unsafeCoerce
-
-#endif
-
-#if MIN_VERSION_base(4,7,0)
+import Data.Time (TimeOfDay(..))
+import Data.Time.Clock.Compat
 
 toPico :: Integer -> Pico
 toPico = MkFixed
 
 fromPico :: Pico -> Integer
 fromPico (MkFixed i) = i
-
-#else
-
-toPico :: Integer -> Pico
-toPico = unsafeCoerce
-
-fromPico :: Pico -> Integer
-fromPico = unsafeCoerce
-
-#endif
 
 -- | Like TimeOfDay, but using a fixed-width integer for seconds.
 data TimeOfDay64 = TOD {-# UNPACK #-} !Int
diff --git a/benchmarks/aeson-benchmarks.cabal b/benchmarks/aeson-benchmarks.cabal
--- a/benchmarks/aeson-benchmarks.cabal
+++ b/benchmarks/aeson-benchmarks.cabal
@@ -72,7 +72,6 @@
 
     exposed-modules:
       Data.Aeson
-      Data.Aeson.Compat
       Data.Aeson.Encoding
       Data.Aeson.Encoding.Builder
       Data.Aeson.Encoding.Internal
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,65 @@
 For the latest version of this document, please see [https://github.com/bos/aeson/blob/master/changelog.md](https://github.com/bos/aeson/blob/master/changelog.md).
 
+### 1.4.7.0
+
+Long overdue release (once again), so there's quite a bit of stuff
+included even though it's a "minor" release. Big thanks to all the
+contributors, the project would not exist without you!
+
+Special thanks to Oleg Grenrus and Xia Li-Yao for reviewing tons
+of stuff.
+
+New stuff:
+
+* Add `rejectUnknownFields` to Options which rejects unknown fields on
+  deserialization. Useful to find errors during development, but
+  enabling this should be considered a breaking change as previously
+  accepted inputs may now be rejected. Thanks to rmanne.
+
+```
+instance FromJSON Foo where
+  parseJSON = gParseJSON defaultOptions { rejectUnknownFields = True }
+```
+
+* `FromJSON` instance of `Ratio a` now parses numbers in addtion to
+  standard `{numerator=..., denumerator=...}` encoding. Thanks to
+  Aleksey Khudyakov.
+
+* Add more information to parse errors, including a sample of the
+  surrounding text. Hopefully this will lead to less "Failed to read:
+  satisfy" confusion! Thanks to Sasha Bogicevic. We expect some
+  downstream test suites to break because of this, apologies in
+  advance. Hopefully you will like the improvement anyway :-)
+
+* Add `parseFail` to `Data.Aeson.Types`. `parseFail = fail` but
+  doesn't require users to know about `MonadFail`. Thanks to Colin
+  Woodbury.
+
+* Make Template Haskell type family detection smarter when deriving
+  `ToJSON1` instances, thanks to Ryan Scott.
+
+* Optimize string parsing for the common case of strings without
+  escapes, thanks to Yuras.
+
+
+Misc:
+
+* Clean up compiler warnings and switch from base-compat to
+  base-compat-batteries. Thanks to Colin Woodbury & Oleg Grenrus.
+
+* Clarification & fixes to documentation regarding treatment of Maybe fields, thanks to Roman Cheplyaka.
+
+* Add documentation for internal development workflows. Thanks to Guru
+  Devanla.
+
+* Drop support for GHC < 7.8. We've chosen to support older GHCs as
+  long as it doesn't prevent us from adding new features, but now it
+  does!  Thanks to Oleg Grenrus for the patch.
+
+* Allow generic-deriving 1.13 in test suite.
+
+* Some DRY fixes thanks to Mark Fajkus.
+
 ### 1.4.6.0
 
 * Provide a clearer error message when a required tagKey for a constructor is missing, thanks to Guru Devanla.
diff --git a/tests/Encoders.hs b/tests/Encoders.hs
--- a/tests/Encoders.hs
+++ b/tests/Encoders.hs
@@ -188,7 +188,6 @@
 gSomeTypeParseJSON2ElemArray :: Value -> Parser (SomeType Int)
 gSomeTypeParseJSON2ElemArray = genericParseJSON opts2ElemArray
 
-#if __GLASGOW_HASKELL__ >= 706
 gSomeTypeLiftToEncoding2ElemArray :: LiftToEncoding SomeType a
 gSomeTypeLiftToEncoding2ElemArray = genericLiftToEncoding opts2ElemArray
 
@@ -197,7 +196,6 @@
 
 gSomeTypeLiftParseJSON2ElemArray :: LiftParseJSON SomeType a
 gSomeTypeLiftParseJSON2ElemArray = genericLiftParseJSON opts2ElemArray
-#endif
 
 
 gSomeTypeToJSONTaggedObject :: SomeType Int -> Value
@@ -209,7 +207,6 @@
 gSomeTypeParseJSONTaggedObject :: Value -> Parser (SomeType Int)
 gSomeTypeParseJSONTaggedObject = genericParseJSON optsTaggedObject
 
-#if __GLASGOW_HASKELL__ >= 706
 gSomeTypeLiftToEncodingTaggedObject :: LiftToEncoding SomeType a
 gSomeTypeLiftToEncodingTaggedObject = genericLiftToEncoding optsTaggedObject
 
@@ -218,7 +215,6 @@
 
 gSomeTypeLiftParseJSONTaggedObject :: LiftParseJSON SomeType a
 gSomeTypeLiftParseJSONTaggedObject = genericLiftParseJSON optsTaggedObject
-#endif
 
 
 gSomeTypeToJSONObjectWithSingleField :: SomeType Int -> Value
@@ -230,7 +226,6 @@
 gSomeTypeParseJSONObjectWithSingleField :: Value -> Parser (SomeType Int)
 gSomeTypeParseJSONObjectWithSingleField = genericParseJSON optsObjectWithSingleField
 
-#if __GLASGOW_HASKELL__ >= 706
 gSomeTypeLiftToEncodingObjectWithSingleField :: LiftToEncoding SomeType a
 gSomeTypeLiftToEncodingObjectWithSingleField = genericLiftToEncoding optsObjectWithSingleField
 
@@ -239,7 +234,6 @@
 
 gSomeTypeLiftParseJSONObjectWithSingleField :: LiftParseJSON SomeType a
 gSomeTypeLiftParseJSONObjectWithSingleField = genericLiftParseJSON optsObjectWithSingleField
-#endif
 
 
 gSomeTypeToJSONOmitNothingFields :: SomeType Int -> Value
@@ -247,6 +241,31 @@
 
 gSomeTypeToEncodingOmitNothingFields :: SomeType Int -> Encoding
 gSomeTypeToEncodingOmitNothingFields = genericToEncoding optsOmitNothingFields
+
+
+thSomeTypeParseJSONRejectUnknownFields :: Value -> Parser (SomeType Int)
+thSomeTypeParseJSONRejectUnknownFields = $(mkParseJSON optsRejectUnknownFields ''SomeType)
+
+gSomeTypeParseJSONRejectUnknownFields :: Value -> Parser (SomeType Int)
+gSomeTypeParseJSONRejectUnknownFields = genericParseJSON optsRejectUnknownFields
+
+
+--------------------------------------------------------------------------------
+-- Foo decoders
+--------------------------------------------------------------------------------
+
+thFooParseJSONRejectUnknownFields :: Value -> Parser Foo
+thFooParseJSONRejectUnknownFields = $(mkParseJSON optsRejectUnknownFields ''Foo)
+
+gFooParseJSONRejectUnknownFields :: Value -> Parser Foo
+gFooParseJSONRejectUnknownFields = genericParseJSON optsRejectUnknownFields
+
+
+thFooParseJSONRejectUnknownFieldsTagged :: Value -> Parser Foo
+thFooParseJSONRejectUnknownFieldsTagged = $(mkParseJSON optsRejectUnknownFieldsTagged ''Foo)
+
+gFooParseJSONRejectUnknownFieldsTagged :: Value -> Parser Foo
+gFooParseJSONRejectUnknownFieldsTagged = genericParseJSON optsRejectUnknownFieldsTagged
 
 
 --------------------------------------------------------------------------------
diff --git a/tests/ErrorMessages.hs b/tests/ErrorMessages.hs
--- a/tests/ErrorMessages.hs
+++ b/tests/ErrorMessages.hs
@@ -136,6 +136,29 @@
       , "[1,"
       ]
 
+  , testWithSomeType "SomeType (reject unknown fields)"
+      (select
+        thSomeTypeParseJSONRejectUnknownFields
+        gSomeTypeParseJSONRejectUnknownFields)
+      [ "{\"tag\": \"record\", \"testOne\": 1.0, \"testZero\": 1}"
+      , "{\"testZero\": 1}"
+      , "{\"tag\": \"record\", \"testone\": true, \"testtwo\": null, \"testthree\": null}"
+      ]
+
+  , testWithFoo "Foo (reject unknown fields)"
+      (select
+        thFooParseJSONRejectUnknownFields
+        gFooParseJSONRejectUnknownFields)
+      [ "{\"tag\": \"foo\"}"
+      ]
+
+  , testWithFoo "Foo (reject unknown fields, tagged single)"
+      (select
+        thFooParseJSONRejectUnknownFieldsTagged
+        gFooParseJSONRejectUnknownFieldsTagged)
+      [ "{\"tag\": \"foo\", \"unknownField\": 0}"
+      ]
+
   , testWith "EitherTextInt"
       (select
         thEitherTextIntParseJSONUntaggedValue
@@ -196,3 +219,6 @@
 
 testWithSomeType :: String -> (Value -> Parser (SomeType Int)) -> [L.ByteString] -> Output
 testWithSomeType = testWith
+
+testWithFoo :: String -> (Value -> Parser Foo) -> [L.ByteString] -> Output
+testWithFoo = testWith
diff --git a/tests/Instances.hs b/tests/Instances.hs
--- a/tests/Instances.hs
+++ b/tests/Instances.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE NoImplicitPrelude #-}
@@ -25,14 +24,12 @@
 
 import Data.Orphans ()
 import Test.QuickCheck.Instances ()
-#if MIN_VERSION_base(4,7,0)
 import Data.Hashable.Time ()
-#endif
 
 -- "System" types.
 
 instance Arbitrary DotNetTime where
-    arbitrary = DotNetTime `liftM` arbitrary
+    arbitrary = DotNetTime `fmap` arbitrary
     shrink = map DotNetTime . shrink . fromDotNetTime
 
 -- | Compare timezone part only on 'timeZoneMinutes'
diff --git a/tests/Options.hs b/tests/Options.hs
--- a/tests/Options.hs
+++ b/tests/Options.hs
@@ -55,3 +55,14 @@
                   { fieldLabelModifier = const "field"
                   , omitNothingFields = True
                   }
+
+optsRejectUnknownFields :: Options
+optsRejectUnknownFields = optsDefault
+                          { rejectUnknownFields = True
+                          }
+
+optsRejectUnknownFieldsTagged :: Options
+optsRejectUnknownFieldsTagged = optsDefault
+                                { rejectUnknownFields = True
+                                , tagSingleConstructors = True
+                                }
diff --git a/tests/PropertyGeneric.hs b/tests/PropertyGeneric.hs
--- a/tests/PropertyGeneric.hs
+++ b/tests/PropertyGeneric.hs
@@ -44,11 +44,9 @@
           , testProperty "TaggedObject" (toParseJSON gSomeTypeParseJSONTaggedObject gSomeTypeToJSONTaggedObject)
           , testProperty "ObjectWithSingleField" (toParseJSON gSomeTypeParseJSONObjectWithSingleField gSomeTypeToJSONObjectWithSingleField)
 
-#if __GLASGOW_HASKELL__ >= 706
           , testProperty "2ElemArray unary" (toParseJSON1 gSomeTypeLiftParseJSON2ElemArray gSomeTypeLiftToJSON2ElemArray)
           , testProperty "TaggedObject unary" (toParseJSON1 gSomeTypeLiftParseJSONTaggedObject gSomeTypeLiftToJSONTaggedObject)
           , testProperty "ObjectWithSingleField unary" (toParseJSON1 gSomeTypeLiftParseJSONObjectWithSingleField gSomeTypeLiftToJSONObjectWithSingleField)
-#endif
           ]
         ]
       , testGroup "OneConstructor" [
@@ -85,30 +83,24 @@
 
       , testProperty "SomeType2ElemArray" $
         gSomeTypeToJSON2ElemArray `sameAs` gSomeTypeToEncoding2ElemArray
-#if __GLASGOW_HASKELL__ >= 706
       , testProperty "SomeType2ElemArray unary" $
         gSomeTypeLiftToJSON2ElemArray `sameAs1` gSomeTypeLiftToEncoding2ElemArray
       , testProperty "SomeType2ElemArray unary agree" $
         gSomeTypeToEncoding2ElemArray `sameAs1Agree` gSomeTypeLiftToEncoding2ElemArray
-#endif
 
       , testProperty "SomeTypeTaggedObject" $
         gSomeTypeToJSONTaggedObject `sameAs` gSomeTypeToEncodingTaggedObject
-#if __GLASGOW_HASKELL__ >= 706
       , testProperty "SomeTypeTaggedObject unary" $
         gSomeTypeLiftToJSONTaggedObject `sameAs1` gSomeTypeLiftToEncodingTaggedObject
       , testProperty "SomeTypeTaggedObject unary agree" $
         gSomeTypeToEncodingTaggedObject `sameAs1Agree` gSomeTypeLiftToEncodingTaggedObject
-#endif
 
       , testProperty "SomeTypeObjectWithSingleField" $
         gSomeTypeToJSONObjectWithSingleField `sameAs` gSomeTypeToEncodingObjectWithSingleField
-#if __GLASGOW_HASKELL__ >= 706
       , testProperty "SomeTypeObjectWithSingleField unary" $
         gSomeTypeLiftToJSONObjectWithSingleField `sameAs1` gSomeTypeLiftToEncodingObjectWithSingleField
       , testProperty "SomeTypeObjectWithSingleField unary agree" $
         gSomeTypeToEncodingObjectWithSingleField `sameAs1Agree` gSomeTypeLiftToEncodingObjectWithSingleField
-#endif
 
       , testProperty "SomeTypeOmitNothingFields" $
         gSomeTypeToJSONOmitNothingFields `sameAs` gSomeTypeToEncodingOmitNothingFields
diff --git a/tests/PropertyKeys.hs b/tests/PropertyKeys.hs
--- a/tests/PropertyKeys.hs
+++ b/tests/PropertyKeys.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE NoImplicitPrelude #-}
 
 module PropertyKeys ( keysTests ) where
@@ -31,12 +30,10 @@
     , testProperty "Natural" $ roundTripKey (undefined :: Natural)
     , testProperty "Float" $ roundTripKey (undefined :: Float)
     , testProperty "Double" $ roundTripKey (undefined :: Double)
-#if MIN_VERSION_base(4,7,0)
     , testProperty "Day" $ roundTripKey (undefined :: Day)
     , testProperty "LocalTime" $ roundTripKey (undefined :: LocalTime)
     , testProperty "TimeOfDay" $ roundTripKey (undefined :: TimeOfDay)
     , testProperty "UTCTime" $ roundTripKey (undefined :: UTCTime)
-#endif
     , testProperty "Version" $ roundTripKey (undefined :: Version)
     , testProperty "Lazy Text" $ roundTripKey (undefined :: LT.Text)
     , testProperty "UUID" $ roundTripKey UUID.nil
diff --git a/tests/SerializationFormatSpec.hs b/tests/SerializationFormatSpec.hs
--- a/tests/SerializationFormatSpec.hs
+++ b/tests/SerializationFormatSpec.hs
@@ -3,9 +3,7 @@
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE OverloadedStrings #-}
-#if __GLASGOW_HASKELL__ >= 708
 {-# LANGUAGE DataKinds #-}
-#endif
 
 ------------------------------------------------------------------------------
 -- These tests assert that the JSON serialization doesn't change by accident.
@@ -77,10 +75,8 @@
   , example "Just"  "1"  (Just 1 :: Maybe Int)
   , example "Proxy Int" "null"  (Proxy :: Proxy Int)
   , example "Tagged Char Int" "1"  (Tagged 1 :: Tagged Char Int)
-#if __GLASGOW_HASKELL__ >= 708
     -- Test Tagged instance is polykinded
   , example "Tagged 123 Int" "1"  (Tagged 1 :: Tagged 123 Int)
-#endif
   , example "Const Char Int" "\"c\""  (Const 'c' :: Const Char Int)
   , example "Tuple" "[1,2]"  ((1, 2) :: (Int, Int))
   , example "NonEmpty" "[1,2,3]"  (1 :| [2, 3] :: NonEmpty Int)
diff --git a/tests/Types.hs b/tests/Types.hs
--- a/tests/Types.hs
+++ b/tests/Types.hs
@@ -119,9 +119,7 @@
 deriving instance Generic (Approx a)
 deriving instance Generic Nullary
 deriving instance Generic (SomeType a)
-#if __GLASGOW_HASKELL__ >= 706
 deriving instance Generic1 SomeType
-#endif
 deriving instance Generic OptionField
 deriving instance Generic EitherTextInt
 
diff --git a/tests/UnitTests.hs b/tests/UnitTests.hs
--- a/tests/UnitTests.hs
+++ b/tests/UnitTests.hs
@@ -5,6 +5,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE QuasiQuotes #-}
 
 -- For Data.Aeson.Types.camelTo
@@ -33,15 +34,15 @@
   ( json, jsonLast, jsonAccum, jsonNoDup
   , json', jsonLast', jsonAccum', jsonNoDup')
 import Data.Aeson.Types
-  ( Options(..), Result(Success), ToJSON(..), Value(Array, Bool, Null, Object)
-  , camelTo, camelTo2, defaultOptions, formatPath, formatRelativePath
-  , omitNothingFields, parse)
+  ( Options(..), Result(Success, Error), ToJSON(..)
+  , Value(Array, Bool, Null, Number, Object, String), camelTo, camelTo2
+  , defaultOptions, formatPath, formatRelativePath, omitNothingFields, parse)
 import Data.Attoparsec.ByteString (Parser, parseOnly)
 import Data.Char (toUpper)
 import Data.Either.Compat (isLeft, isRight)
 import Data.Hashable (hash)
 import Data.HashMap.Strict (HashMap)
-import Data.List (sort)
+import Data.List (sort, isSuffixOf)
 import Data.Maybe (fromMaybe)
 import Data.Scientific (Scientific, scientific)
 import Data.Tagged (Tagged(..))
@@ -112,8 +113,12 @@
   , testGroup "SingleMaybeField" singleMaybeField
   , testCase "withEmbeddedJSON" withEmbeddedJSONTest
   , testCase "SingleFieldCon" singleFieldCon
+  , testGroup "UnknownFields" unknownFields
   , testGroup "Ordering of object keys" keyOrdering
   , testCase "Ratio with denominator 0" ratioDenominator0
+  , testCase "Rational parses number"   rationalNumber
+  , testCase "Big rational"             bigRationalDecoding
+  , testCase "Small rational"           smallRationalDecoding
   , testCase "Big scientific exponent" bigScientificExponent
   , testCase "Big integer decoding" bigIntegerDecoding
   , testCase "Big natural decading" bigNaturalDecoding
@@ -536,6 +541,7 @@
         ++ ", sumEncoding = TaggedObject {tagFieldName = \"tag\", contentsFieldName = \"contents\"}"
         ++ ", unwrapUnaryRecords = False"
         ++ ", tagSingleConstructors = False"
+        ++ ", rejectUnknownFields = False"
         ++ "}")
         (show defaultOptions)
 
@@ -580,6 +586,79 @@
 singleFieldCon =
   assertEqual "fromJSON" (Right (SingleFieldCon 0)) (eitherDecode "0")
 
+newtype UnknownFields = UnknownFields { knownField :: Int }
+  deriving (Eq, Show, Generic)
+newtype UnknownFieldsTag = UnknownFieldsTag { tag :: Int }
+  deriving (Eq, Show, Generic)
+newtype UnknownFieldsUnaryTagged = UnknownFieldsUnaryTagged { knownFieldUnaryTagged :: Int }
+  deriving (Eq, Show, Generic)
+data UnknownFieldsSum
+  = UnknownFields1 { knownField1 :: Int }
+  | UnknownFields2 { knownField2 :: Int }
+  deriving (Eq, Show, Generic)
+
+unknownFields :: [TestTree]
+unknownFields = concat
+    [ testsUnary
+        "unary-unknown"
+        (object [("knownField", Number 1), ("unknownField", Number 1)])
+        (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFields)
+    , testsUnary
+        "unary-unknown-tag"
+        (object [("knownField", Number 1), ("tag", String "UnknownFields")])
+        (Error "nknown fields: [\"tag\"]" :: Result UnknownFields)
+    , testsUnaryTag
+        "unary-explicit-tag"
+        (object [("tag", Number 1)])
+        (Success $ UnknownFieldsTag 1)
+    , testsSum
+        "sum-tag"
+        (object [("knownField1", Number 1), ("tag", String "UnknownFields1")])
+        (Success $ UnknownFields1 1)
+    , testsSum
+        "sum-unknown-in-branch"
+        (object [("knownField1", Number 1), ("knownField2", Number 1), ("tag", String "UnknownFields1")])
+        (Error "nknown fields: [\"knownField2\"]" :: Result UnknownFieldsSum)
+    , testsSum
+        "sum-unknown"
+        (object [("knownField1", Number 1), ("unknownField", Number 1), ("tag", String "UnknownFields1")])
+        (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFieldsSum)
+    , testsTagged
+        "unary-tagged"
+        (object [("knownFieldUnaryTagged", Number 1), ("tag", String "UnknownFieldsUnaryTagged")])
+        (Success $ UnknownFieldsUnaryTagged 1)
+    , -- Just a case to verify that the tag isn't optional, this is likely already tested by other unit tests
+      testsTagged
+        "unary-tagged-notag"
+        (object [("knownFieldUnaryTagged", Number 1)])
+        (Error "key \"tag\" not found" :: Result UnknownFieldsUnaryTagged)
+    , testsTagged
+        "unary-tagged-unknown"
+        (object [ ("knownFieldUnaryTagged", Number 1), ("unknownField", Number 1)
+                , ("tag", String "UnknownFieldsUnaryTagged")])
+        (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFieldsUnaryTagged)
+    ]
+    where
+        opts = defaultOptions{rejectUnknownFields=True}
+        taggedOpts = opts{tagSingleConstructors=True}
+        assertApprox :: (Show a, Eq a) => Result a -> Result a -> IO ()
+        assertApprox (Error expected) (Error actual) | expected `isSuffixOf` actual = return ()
+        assertApprox expected actual = assertEqual "fromJSON" expected actual
+        testsBase :: (Show a, Eq a) => (Value -> Result a) -> (Value -> Result a)
+                                    -> String -> Value -> Result a -> [TestTree]
+        testsBase th g name value expected =
+            [ testCase (name ++ "-th") $ assertApprox expected (th value)
+            , testCase (name ++ "-generic") $ assertApprox expected (g value)
+            ]
+        testsUnary :: String -> Value -> Result UnknownFields -> [TestTree]
+        testsUnary = testsBase fromJSON (parse (genericParseJSON opts))
+        testsUnaryTag :: String -> Value -> Result UnknownFieldsTag -> [TestTree]
+        testsUnaryTag = testsBase fromJSON (parse (genericParseJSON opts))
+        testsSum :: String -> Value -> Result UnknownFieldsSum -> [TestTree]
+        testsSum = testsBase fromJSON (parse (genericParseJSON opts))
+        testsTagged :: String -> Value -> Result UnknownFieldsUnaryTagged -> [TestTree]
+        testsTagged = testsBase fromJSON (parse (genericParseJSON taggedOpts))
+
 testParser :: (Eq a, Show a)
            => String -> Parser a -> S.ByteString -> Either String a -> TestTree
 testParser name json_ s expected =
@@ -620,6 +699,25 @@
     (Left "Error in $: Ratio denominator was 0")
     (eitherDecode "{ \"numerator\": 1, \"denominator\": 0 }" :: Either String Rational)
 
+rationalNumber :: Assertion
+rationalNumber =
+  assertEqual "Ratio with denominator 0"
+    (Right 1.37)
+    (eitherDecode "1.37" :: Either String Rational)
+
+bigRationalDecoding :: Assertion
+bigRationalDecoding =
+  assertEqual "Decoding an Integer with a large exponent should fail"
+    (Left "Error in $: parsing Ratio failed, found a number with exponent 2000, but it must not be greater than 1024 or less than -1024")
+    ((eitherDecode :: L.ByteString -> Either String Rational) "1e2000")
+
+smallRationalDecoding :: Assertion
+smallRationalDecoding =
+  assertEqual "Decoding an Integer with a large exponent should fail"
+    (Left "Error in $: parsing Ratio failed, found a number with exponent -2000, but it must not be greater than 1024 or less than -1024")
+    ((eitherDecode :: L.ByteString -> Either String Rational) "1e-2000")
+
+
 bigScientificExponent :: Assertion
 bigScientificExponent =
   assertEqual "Encoding an integral scientific with a large exponent should normalize it"
@@ -650,9 +748,21 @@
     (Left "Error in $['1e2000']: found a number with exponent 2000, but it must not be greater than 1024")
     ((eitherDecode :: L.ByteString -> Either String (HashMap Natural Value)) "{ \"1e2000\": null }")
 
+-- A regression test for: https://github.com/bos/aeson/issues/757
+type family Fam757 :: * -> *
+type instance Fam757 = Maybe
+newtype Newtype757 a = MkNewtype757 (Fam757 a)
+
 deriveJSON defaultOptions{omitNothingFields=True} ''MyRecord
 
 deriveToJSON  defaultOptions ''Foo
 deriveToJSON1 defaultOptions ''Foo
 
 deriveJSON defaultOptions{omitNothingFields=True,unwrapUnaryRecords=True} ''SingleMaybeField
+
+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFields
+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFieldsTag
+deriveJSON defaultOptions{tagSingleConstructors=True,rejectUnknownFields=True} ''UnknownFieldsUnaryTagged
+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFieldsSum
+
+deriveToJSON1 defaultOptions ''Newtype757
diff --git a/tests/golden/generic.expected b/tests/golden/generic.expected
--- a/tests/golden/generic.expected
+++ b/tests/golden/generic.expected
@@ -31,6 +31,14 @@
 Error in $: parsing Types.SomeType failed, expected Array, but encountered Object
 Error in $: not enough input. Expecting ',' or ']'
 Error in $: not enough input. Expecting json list value
+SomeType (reject unknown fields)
+Error in $: parsing Types.SomeType(Record) failed, unknown fields: ["testZero"]
+Error in $: parsing Types.SomeType failed, expected Object with key "tag" containing one of ["nullary","unary","product","record","list"], key "tag" not found
+Error in $: parsing Types.SomeType(Record) failed, unknown fields: ["testtwo","testone","testthree"]
+Foo (reject unknown fields)
+Error in $: parsing Types.Foo(Foo) failed, unknown fields: ["tag"]
+Foo (reject unknown fields, tagged single)
+Error in $: parsing Types.Foo(Foo) failed, unknown fields: ["unknownField"]
 EitherTextInt
 Error in $: parsing Types.EitherTextInt(NoneNullary) failed, expected tag "nonenullary", but found tag "X"
 Error in $: parsing Types.EitherTextInt(NoneNullary) failed, expected String, but encountered Array
