diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Native Haskell implementation of Avro
 
-[![CircleCI](https://circleci.com/gh/haskell-works/avro.svg?style=svg)](https://circleci.com/gh/haskell-works/avro)
+[![Binaries](https://github.com/haskell-works/avro/actions/workflows/haskell.yml/badge.svg)](https://github.com/haskell-works/avro/actions/workflows/haskell.yml)
 [![Hackage](https://img.shields.io/hackage/v/avro.svg?logo=haskell)](https://hackage.haskell.org/package/avro)
 
 This is a Haskell [Avro](https://avro.apache.org/) library useful for decoding
@@ -29,7 +29,7 @@
     { "name": "fullName", "type": "string" },
     { "name": "age", "type": "int" },
     { "name": "gender",
-      "type": { "type": "enum", "symbols": ["Male", "Female"] }
+      "type": { "name": "Gender", "type": "enum", "symbols": ["Male", "Female"] }
     },
     { "name": "ssn", "type": ["null", "string"] }
   ]
@@ -196,28 +196,30 @@
 
 This library provides the following conversions between Haskell types and Avro types:
 
-| Haskell type      | Avro type                                               |
-|:------------------|:--------------------------------------------------------|
-| ()                | "null"                                                  |
-| Bool              | "boolean"                                               |
-| Int, Int64        | "long"                                                  |
-| Int32             | "int"                                                   |
-| Double            | "double"                                                |
-| Text              | "string"                                                |
-| ByteString        | "bytes"                                                 |
-| Maybe a           | ["null", "a"]                                           |
-| Either a b        | ["a", "b"]                                              |
-| Identity a        | ["a"]                                                   |
-| Map Text a        | { "type": "map",    "value": "a" }                      |
-| Map String a      | { "type": "map",    "value": "a" }                      |
-| HashMap Text a    | { "type": "map",    "value": "a" }                      |
-| HashMap String a  | { "type": "map",    "value": "a" }                      |
-| [a]               | { "type": "array",  "value": "a" }                      |
-| UTCTime           | { "type": "long",   "logicalType": "timestamp-millis" } |
-| UTCTime           | { "type": "long",   "logicalType": "timestamp-micros" } |
-| DiffTime          | { "type": "int",    "logicalType": "time-millis" }      |
-| DiffTime          | { "type": "long",   "logicalType": "time-micros" }      |
-| Day               | { "type": "int",    "logicalType": "date" }             |
-| UUID              | { "type": "string", "logicalType": "uuid" }             |
+| Haskell type     | Avro type                                                     |
+|:-----------------|:--------------------------------------------------------------|
+| ()               | "null"                                                        |
+| Bool             | "boolean"                                                     |
+| Int, Int64       | "long"                                                        |
+| Int32            | "int"                                                         |
+| Double           | "double"                                                      |
+| Text             | "string"                                                      |
+| ByteString       | "bytes"                                                       |
+| Maybe a          | ["null", "a"]                                                 |
+| Either a b       | ["a", "b"]                                                    |
+| Identity a       | ["a"]                                                         |
+| Map Text a       | { "type": "map",    "value": "a" }                            |
+| Map String a     | { "type": "map",    "value": "a" }                            |
+| HashMap Text a   | { "type": "map",    "value": "a" }                            |
+| HashMap String a | { "type": "map",    "value": "a" }                            |
+| [a]              | { "type": "array",  "value": "a" }                            |
+| UTCTime          | { "type": "long",   "logicalType": "timestamp-millis" }       |
+| UTCTime          | { "type": "long",   "logicalType": "timestamp-micros" }       |
+| LocalTime        | { "type": "long",   "logicalType": "local-timestamp-millis" } |
+| LocalTime        | { "type": "long",   "logicalType": "local-timestamp-micros" } |
+| DiffTime         | { "type": "int",    "logicalType": "time-millis" }            |
+| DiffTime         | { "type": "long",   "logicalType": "time-micros" }            |
+| Day              | { "type": "int",    "logicalType": "date" }                   |
+| UUID             | { "type": "string", "logicalType": "uuid" }                   |
 
 User defined data types should provide `HasAvroSchema` / `ToAvro` / `FromAvro` instances to be encoded/decoded to/from Avro.
diff --git a/avro.cabal b/avro.cabal
--- a/avro.cabal
+++ b/avro.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.4
 
 name:                   avro
-version:                0.5.2.1
+version:                0.6.2.1
 synopsis:               Avro serialization support for Haskell
 description:            Avro serialization and deserialization support for Haskell
 category:               Data
@@ -11,7 +11,7 @@
 maintainer:             Alexey Raga <alexey.raga@gmail.com>
 license:                BSD-3-Clause
 license-file:           LICENSE
-tested-with:            GHC == 8.10.2, GHC == 8.8.3, GHC == 8.6.5
+tested-with:            GHC == 9.10.1, GHC == 9.8.2, GHC == 9.6.6
 build-type:             Simple
 extra-source-files:     README.md
                         ChangeLog.md
@@ -44,50 +44,54 @@
   manual: True
   default: False
 
-common base                     { build-depends: base                     >= 4          && < 5      }
+common base                     { build-depends: base                     >= 4          && < 5                      }
 
-common aeson                    { build-depends: aeson                                              }
-common array                    { build-depends: array                                              }
-common base16-bytestring        { build-depends: base16-bytestring                                  }
-common bifunctors               { build-depends: bifunctors                                         }
-common big-decimal              { build-depends: HasBigDecimal                                      }
-common binary                   { build-depends: binary                                             }
-common bytestring               { build-depends: bytestring                                         }
-common containers               { build-depends: containers                                         }
-common data-binary-ieee754      { build-depends: data-binary-ieee754                                }
-common deepseq                  { build-depends: deepseq                                            }
-common directory                { build-depends: directory                                          }
-common doctest                  { build-depends: doctest                  >= 0.16.2     && < 0.19   }
-common doctest-discover         { build-depends: doctest-discover         >= 0.2        && < 0.3    }
-common extra                    { build-depends: extra                                              }
-common fail                     { build-depends: fail                                               }
-common gauge                    { build-depends: gauge                                              }
-common generic-lens             { build-depends: generic-lens             >= 1.2        && < 2.1    }
-common hashable                 { build-depends: hashable                                           }
-common hedgehog                 { build-depends: hedgehog                                           }
-common hw-hspec-hedgehog        { build-depends: hw-hspec-hedgehog                                  }
-common hspec                    { build-depends: hspec                                              }
-common lens                     { build-depends: lens                                               }
-common lens-aeson               { build-depends: lens-aeson                                         }
-common mtl                      { build-depends: mtl                                                }
-common QuickCheck               { build-depends: QuickCheck                                         }
-common random                   { build-depends: random                                             }
-common raw-strings-qq           { build-depends: raw-strings-qq                                     }
-common scientific               { build-depends: scientific                                         }
-common semigroups               { build-depends: semigroups                                         }
-common tagged                   { build-depends: tagged                                             }
-common text                     { build-depends: text                     >= 1.2.3      && < 1.3    }
-common time                     { build-depends: time                                               }
-common template-haskell         { build-depends: template-haskell         >= 2.4        && < 3      }
-common tf-random                { build-depends: tf-random                                          }
-common transformers             { build-depends: transformers                                       }
-common unordered-containers     { build-depends: unordered-containers                               }
-common uuid                     { build-depends: uuid                                               }
-common vector                   { build-depends: vector                                             }
-common zlib                     { build-depends: zlib                                               }
+common aeson                    { build-depends: aeson                    >= 2.0.1.0                                }
+common array                    { build-depends: array                                                              }
+common base16-bytestring        { build-depends: base16-bytestring                                                  }
+common bifunctors               { build-depends: bifunctors                                                         }
+common big-decimal              { build-depends: HasBigDecimal            >= 0.2        && < 0.3                    }
+common binary                   { build-depends: binary                                                             }
+common bytestring               { build-depends: bytestring                                                         }
+common containers               { build-depends: containers                                                         }
+common data-binary-ieee754      { build-depends: data-binary-ieee754                                                }
+common deepseq                  { build-depends: deepseq                                                            }
+common directory                { build-depends: directory                                                          }
+common doctest                  { build-depends: doctest                  >= 0.16.2     && < 0.23                   }
+common doctest-discover         { build-depends: doctest-discover         >= 0.2        && < 0.3                    }
+common extra                    { build-depends: extra                                                              }
+common fail                     { build-depends: fail                                                               }
+common generic-lens             { build-depends: generic-lens             >= 1.2        && < 2.3                    }
+common hashable                 { build-depends: hashable                                                           }
+common hedgehog                 { build-depends: hedgehog                                                           }
+common hw-hspec-hedgehog        { build-depends: hw-hspec-hedgehog                                                  }
+common hspec                    { build-depends: hspec                                                              }
+common lens                     { build-depends: lens                                                               }
+common lens-aeson               { build-depends: lens-aeson                                                         }
+common mtl                      { build-depends: mtl                                                                }
+common QuickCheck               { build-depends: QuickCheck                                                         }
+common random                   { build-depends: random                                                             }
+common raw-strings-qq           { build-depends: raw-strings-qq                                                     }
+common scientific               { build-depends: scientific                                                         }
+common semigroups               { build-depends: semigroups                                                         }
+common tagged                   { build-depends: tagged                                                             }
+common text                     { build-depends: text                     >= 1.2.3      && < 1.3 || >= 2.0 && < 2.2 }
+common time                     { build-depends: time                                                               }
+common template-haskell         { build-depends: template-haskell         >= 2.4        && < 3                      }
+common tf-random                { build-depends: tf-random                                                          }
+common th-lift-instances        { build-depends: th-lift-instances                                                  }
+common transformers             { build-depends: transformers             >= 0.5.6.2    && < 0.7                    }
+common unordered-containers     { build-depends: unordered-containers                                               }
+common uuid                     { build-depends: uuid                                                               }
+common vector                   { build-depends: vector                                                             }
+common zlib                     { build-depends: zlib                                                               }
 
+common gauge                    { build-depends: criterion                                                          }
+
 common config
   default-language:     Haskell2010
+  ghc-options:          -Wall
+
   if flag(dev)
     ghc-options:        -Wall -Werror
 
@@ -113,6 +117,7 @@
                       , text
                       , time
                       , tf-random
+                      , th-lift-instances
                       , unordered-containers
                       , uuid
                       , vector
@@ -206,6 +211,7 @@
                         Avro.Data.Maybe
                         Avro.Data.Recursive
                         Avro.Data.Reused
+                        Avro.Data.TwoBits
                         Avro.Data.Unions
                         Avro.Decode.ContainerSpec
                         Avro.Decode.RawBlocksSpec
diff --git a/bench/Bench/Deconflict.hs b/bench/Bench/Deconflict.hs
--- a/bench/Bench/Deconflict.hs
+++ b/bench/Bench/Deconflict.hs
@@ -8,7 +8,7 @@
 )
 where
 
-import Data.Avro                   (decodeContainerWithReaderSchema, decodeValue, decodeValueWithSchema, encodeContainerWithSchema, encodeValueWithSchema, nullCodec)
+import Data.Avro                   (decodeContainerWithReaderSchema, decodeValueWithSchema, encodeContainerWithSchema, encodeValueWithSchema, nullCodec)
 import Data.Avro.Schema.ReadSchema (fromSchema)
 import Data.Vector                 (Vector)
 
@@ -17,7 +17,8 @@
 import qualified Data.Vector             as Vector
 import qualified System.Random           as Random
 
-import Gauge
+-- import Gauge
+import Criterion.Main
 
 newOuter :: IO W.Outer
 newOuter = do
@@ -29,12 +30,12 @@
 many = Vector.replicateM
 
 values :: Benchmark
-values = env (many 1e5 $ encodeValueWithSchema W.schema'Outer <$> newOuter) $ \ values ->
+values = env (many 1e5 $ encodeValueWithSchema W.schema'Outer <$> newOuter) $ \ vs ->
   let
     readSchema = fromSchema W.schema'Outer
   in bgroup "Encoded: ByteString"
       [ bgroup "No Deconflict"
-          [ bench "Read via FromAvro" $ nf (fmap (decodeValueWithSchema @W.Outer readSchema)) values
+          [ bench "Read via FromAvro" $ nf (fmap (decodeValueWithSchema @W.Outer readSchema)) vs
           ]
       ]
 
diff --git a/bench/Bench/Encoding.hs b/bench/Bench/Encoding.hs
--- a/bench/Bench/Encoding.hs
+++ b/bench/Bench/Encoding.hs
@@ -16,17 +16,16 @@
 where
 
 import           Control.DeepSeq
-import           Data.Avro               (decodeContainerWithEmbeddedSchema, encodeContainer, encodeContainerWithSchema, encodeValueWithSchema, nullCodec)
+import           Data.Avro               (decodeContainerWithEmbeddedSchema, encodeValueWithSchema, nullCodec)
 import qualified Data.Avro               as Avro
 import           Data.Avro.Deriving      (deriveAvroFromByteString, r)
-import           Data.ByteString         (ByteString)
-import           Data.ByteString.Builder
 import qualified Data.ByteString.Lazy    as BL
 import           Data.List               (unfoldr)
 import qualified Data.Vector             as Vector
 import qualified System.Random           as Random
 
-import Gauge
+-- import Gauge
+import Criterion.Main
 
 deriveAvroFromByteString [r|
 {
diff --git a/bench/Main.hs b/bench/Main.hs
--- a/bench/Main.hs
+++ b/bench/Main.hs
@@ -1,18 +1,26 @@
+{-# LANGUAGE CPP                 #-}
 {-# LANGUAGE OverloadedLists     #-}
 {-# LANGUAGE ScopedTypeVariables #-}
-module Main
-where
 
+module Main where
+
+-- #if defined(i386_HOST_ARCH) || defined(x86_64_HOST_ARCH)
 import qualified Bench.Deconflict as Deconflict
 import qualified Bench.Encoding   as Encoding
-
-import Gauge
+import Criterion.Main
+-- import Gauge
+-- #endif
 
 main :: IO ()
-main = defaultMain
-  [ Deconflict.values
-  , Encoding.encodeToBS
-  , Encoding.encodeContainer
-  , Encoding.roundtripContainer
-  , Deconflict.container
-  ]
+main =
+-- #if defined(i386_HOST_ARCH) || defined(x86_64_HOST_ARCH)
+  defaultMain
+    [ Deconflict.values
+    , Encoding.encodeToBS
+    , Encoding.encodeContainer
+    , Encoding.roundtripContainer
+    , Deconflict.container
+    ]
+-- #else
+--   return ()
+-- #endif
diff --git a/src/Data/Avro.hs b/src/Data/Avro.hs
--- a/src/Data/Avro.hs
+++ b/src/Data/Avro.hs
@@ -68,7 +68,7 @@
 import qualified Data.Avro.Schema.Schema      as Schema
 import           Data.Binary.Get              (runGetOrFail)
 import           Data.ByteString.Builder      (toLazyByteString)
-import qualified Data.ByteString.Lazy         as BL
+import qualified Data.ByteString.Lazy         as Lazy
 import           Data.Tagged                  (untag)
 
 {- HLINT ignore "Use section"         -}
@@ -80,21 +80,21 @@
 {-# INLINE readSchemaFromSchema #-}
 
 -- | Serialises an individual value into Avro with the schema provided.
-encodeValueWithSchema :: ToAvro a => Schema -> a -> BL.ByteString
+encodeValueWithSchema :: ToAvro a => Schema -> a -> Lazy.ByteString
 encodeValueWithSchema s = toLazyByteString . toAvro s
 {-# INLINE encodeValueWithSchema #-}
 
 -- | Serialises an individual value into Avro using the schema
 -- from its coresponding 'HasAvroSchema' instance.
-encodeValue :: (HasAvroSchema a, ToAvro a) => a -> BL.ByteString
+encodeValue :: (HasAvroSchema a, ToAvro a) => a -> Lazy.ByteString
 encodeValue a = encodeValueWithSchema (schemaOf a) a
 {-# INLINE encodeValue #-}
 
 -- | Deserialises an individual value from Avro.
-decodeValueWithSchema :: FromAvro a => ReadSchema -> BL.ByteString -> Either String a
-decodeValueWithSchema schema payload =
-  case runGetOrFail (getValue schema) payload of
-    Right (bs, _, v) -> fromAvro v
+decodeValueWithSchema :: FromAvro a => ReadSchema -> Lazy.ByteString -> Either String a
+decodeValueWithSchema readSchema payload =
+  case runGetOrFail (getValue readSchema) payload of
+    Right (_, _, v) -> fromAvro v
     Left (_, _, e)   -> Left e
 
 -- | Deserialises an individual value from Avro using the schema from its coresponding 'HasAvroSchema'.
@@ -102,7 +102,7 @@
 -- __NOTE__: __This function is only to be used when reader and writes schemas are known to be the same.__
 -- Because only one schema is known at this point, and it is the reader schema,
 -- /no decondlicting/ can be performed.
-decodeValue :: forall a. (HasAvroSchema a, FromAvro a) => BL.ByteString -> Either String a
+decodeValue :: forall a. (HasAvroSchema a, FromAvro a) => Lazy.ByteString -> Either String a
 decodeValue = decodeValueWithSchema (fromSchema (untag @a schema))
 {-# INLINE decodeValue #-}
 
@@ -112,7 +112,7 @@
 -- error. This means that the consumer will get all the "good" content from
 -- the container until the error is detected, then this error and then the list
 -- is finished.
-decodeContainer :: forall a. (HasAvroSchema a, FromAvro a) => BL.ByteString -> [Either String a]
+decodeContainer :: forall a. (HasAvroSchema a, FromAvro a) => Lazy.ByteString -> [Either String a]
 decodeContainer = decodeContainerWithReaderSchema (untag @a schema)
 {-# INLINE decodeContainer #-}
 
@@ -122,7 +122,7 @@
 -- error. This means that the consumer will get all the "good" content from
 -- the container until the error is detected, then this error and then the list
 -- is finished.
-decodeContainerWithEmbeddedSchema :: forall a. FromAvro a => BL.ByteString -> [Either String a]
+decodeContainerWithEmbeddedSchema :: forall a. FromAvro a => Lazy.ByteString -> [Either String a]
 decodeContainerWithEmbeddedSchema payload =
   case Container.extractContainerValues (pure . fromSchema) (getValue >=> (either fail pure . fromAvro)) payload of
     Left err          -> [Left err]
@@ -137,7 +137,7 @@
 -- error. This means that the consumer will get all the "good" content from
 -- the container until the error is detected, then this error and then the list
 -- is finished.
-decodeContainerWithReaderSchema :: forall a. FromAvro a => Schema -> BL.ByteString -> [Either String a]
+decodeContainerWithReaderSchema :: forall a. FromAvro a => Schema -> Lazy.ByteString -> [Either String a]
 decodeContainerWithReaderSchema readerSchema payload =
   case Container.extractContainerValues (flip deconflict readerSchema) (getValue >=> (either fail pure . fromAvro)) payload of
     Left err          -> [Left err]
@@ -148,7 +148,7 @@
 -- This is particularly useful when slicing up containers into one or more
 -- smaller files.  By extracting the original bytestring it is possible to
 -- avoid re-encoding data.
-extractContainerValuesBytes :: BL.ByteString -> Either String (Schema, [Either String BL.ByteString])
+extractContainerValuesBytes :: Lazy.ByteString -> Either String (Schema, [Either String Lazy.ByteString])
 extractContainerValuesBytes =
   (fmap . fmap . fmap . fmap) snd . Container.extractContainerValuesBytes (pure . fromSchema) getValue
 {-# INLINE extractContainerValuesBytes #-}
@@ -161,8 +161,8 @@
 -- avoid re-encoding data.
 decodeContainerValuesBytes :: forall a. FromAvro a
   => Schema
-  -> BL.ByteString
-  -> Either String (Schema, [Either String (a, BL.ByteString)])
+  -> Lazy.ByteString
+  -> Either String (Schema, [Either String (a, Lazy.ByteString)])
 decodeContainerValuesBytes readerSchema =
   Container.extractContainerValuesBytes (flip deconflict readerSchema) (getValue >=> (either fail pure . fromAvro))
 {-# INLINE decodeContainerValuesBytes #-}
@@ -170,19 +170,19 @@
 -- | Encode chunks of values into a container, using 16 random bytes for
 -- the synchronization markers and a corresponding 'HasAvroSchema' schema.
 -- Blocks are compressed (or not) according to the given 'Codec' ('nullCodec' or 'deflateCodec').
-encodeContainer :: forall a. (HasAvroSchema a, ToAvro a) => Codec -> [[a]] -> IO BL.ByteString
+encodeContainer :: forall a. (HasAvroSchema a, ToAvro a) => Codec -> [[a]] -> IO Lazy.ByteString
 encodeContainer codec = encodeContainerWithSchema codec (untag @a schema)
 
 -- | Encode chunks of values into a container, using 16 random bytes for
 -- the synchronization markers. Blocks are compressed (or not) according
 -- to the given 'Codec' ('nullCodec' or 'deflateCodec').
-encodeContainerWithSchema :: ToAvro a => Codec -> Schema -> [[a]] -> IO BL.ByteString
+encodeContainerWithSchema :: ToAvro a => Codec -> Schema -> [[a]] -> IO Lazy.ByteString
 encodeContainerWithSchema codec sch xss =
   do sync <- Container.newSyncBytes
      return $ encodeContainerWithSync codec sch sync xss
 
 -- |Encode chunks of objects into a container, using the provided
 -- ByteString as the synchronization markers.
-encodeContainerWithSync :: ToAvro a => Codec -> Schema -> BL.ByteString -> [[a]] -> BL.ByteString
+encodeContainerWithSync :: ToAvro a => Codec -> Schema -> Lazy.ByteString -> [[a]] -> Lazy.ByteString
 encodeContainerWithSync = Container.packContainerValuesWithSync' toAvro
 {-# INLINE encodeContainerWithSync #-}
diff --git a/src/Data/Avro/Codec.hs b/src/Data/Avro/Codec.hs
--- a/src/Data/Avro/Codec.hs
+++ b/src/Data/Avro/Codec.hs
@@ -12,11 +12,10 @@
 import           Codec.Compression.Zlib.Internal as Zlib
 import qualified Data.Binary.Get                 as G
 import           Data.ByteString                 (ByteString)
-import qualified Data.ByteString                 as BS
-import qualified Data.ByteString.Lazy            as LBS
+import qualified Data.ByteString.Lazy            as Lazy
 
 -- | Block decompression function for blocks of Avro.
-type Decompress a = LBS.ByteString -> G.Get a -> Either String a
+type Decompress a = Lazy.ByteString -> G.Get a -> Either String a
 
 -- | A `Codec` allows for compression/decompression of a block in an
 -- Avro container according to the Avro spec.
@@ -33,7 +32,7 @@
   , codecDecompress :: forall a. Decompress a
 
     -- | Compresses a lazy stream of bytes.
-  , codecCompress   :: LBS.ByteString -> LBS.ByteString
+  , codecCompress   :: Lazy.ByteString -> Lazy.ByteString
   }
 
 -- | `nullCodec` specifies @null@ required by Avro spec.
@@ -61,7 +60,7 @@
     , codecCompress   = deflateCompress
     }
 
-deflateCompress :: LBS.ByteString -> LBS.ByteString
+deflateCompress :: Lazy.ByteString -> Lazy.ByteString
 deflateCompress =
   Zlib.compress Zlib.rawFormat Zlib.defaultCompressParams
 
@@ -69,12 +68,12 @@
 -- | Internal type to help construct a lazy list of
 -- decompressed bytes interleaved with errors if any.
 data Chunk
-  = ChunkRest LBS.ByteString
+  = ChunkRest Lazy.ByteString
   | ChunkBytes ByteString
   | ChunkError Zlib.DecompressError
 
 
-deflateDecompress :: forall a. LBS.ByteString -> G.Get a -> Either String a
+deflateDecompress :: forall a. Lazy.ByteString -> G.Get a -> Either String a
 deflateDecompress bytes parser = do
   let
     -- N.B. this list is lazily created which allows us to
diff --git a/src/Data/Avro/Deriving.hs b/src/Data/Avro/Deriving.hs
--- a/src/Data/Avro/Deriving.hs
+++ b/src/Data/Avro/Deriving.hs
@@ -7,10 +7,8 @@
 {-# LANGUAGE TemplateHaskell    #-}
 {-# LANGUAGE TypeApplications   #-}
 
-{- HLINT ignore "Avoid lambda using `infix`" -}
-
 -- | This module lets us derive Haskell types from an Avro schema that
--- can be serialized/deserialzed to Avro.
+-- can be serialized/deserialized to Avro.
 module Data.Avro.Deriving
   ( -- * Deriving options
     DeriveOptions(..)
@@ -34,55 +32,45 @@
   , deriveAvro'
 
   -- * Re-exporting a quasiquoter for raw string literals
-  , r
+  , QQ.r
 )
 where
 
-import           Control.Monad                (join)
-import           Control.Monad.Identity       (Identity)
-import           Data.Aeson                   (eitherDecode)
-import qualified Data.Aeson                   as J
-import           Data.Avro                    hiding (decode, encode)
-import           Data.Avro.Encoding.ToAvro    (ToAvro (..))
-import           Data.Avro.Internal.EncodeRaw (putI)
-import           Data.Avro.Schema.Schema      as S
-import           Data.ByteString              (ByteString)
-import qualified Data.ByteString              as B
-import           Data.Char                    (isAlphaNum)
+import           Control.Monad                ( join )
+import           Control.Monad.Identity       ( Identity )
+
+import           Data.Aeson                   ( eitherDecode )
+import           Data.ByteString              ( ByteString )
+import qualified Data.ByteString              as Strict
+import qualified Data.ByteString.Lazy         as Lazy
 import qualified Data.Foldable                as Foldable
-import           Data.Int
-import           Data.List.NonEmpty           (NonEmpty ((:|)))
-import qualified Data.List.NonEmpty           as NE
-import           Data.Map                     (Map)
-import           Data.Maybe                   (fromMaybe)
-import           Data.Semigroup               ((<>))
+import           Data.Text                    (Text)
 import qualified Data.Text                    as Text
-import           Data.Time                    (Day, DiffTime, UTCTime)
-import           Data.UUID                    (UUID)
-import           Text.RawString.QQ            (r)
+import qualified Data.Vector                  as V
+import           Data.Char                    ( isAlphaNum )
+import           Data.Int                     ( Int32, Int64 )
+import           Data.Map                     ( Map )
+import           Data.Time                    ( Day, DiffTime, LocalTime, UTCTime )
+import           Data.UUID                    ( UUID )
 
-import qualified Data.Avro.Encoding.FromAvro as AV
+import           GHC.Generics                 ( Generic )
+import qualified Language.Haskell.TH as TH
+import           Language.Haskell.TH.Syntax
+import qualified Text.RawString.QQ           as QQ
 
-import GHC.Generics (Generic)
 
-import Language.Haskell.TH        as TH hiding (notStrict)
-import Language.Haskell.TH.Lib    as TH hiding (notStrict)
-import Language.Haskell.TH.Syntax
+import qualified Data.Avro.Encoding.FromAvro as AV
+import           Data.Avro.Encoding.ToAvro    ( ToAvro(..) )
+import           Data.Avro.HasAvroSchema      ( HasAvroSchema )
+import qualified Data.Avro.HasAvroSchema
+import           Data.Avro.Internal.EncodeRaw ( putI )
+import           Data.Avro.Schema.Schema      ( Schema, TypeName, Field )
+import qualified Data.Avro.Schema.Schema      as Schema
+import           Data.Avro.Deriving.Lift ()
+import           Data.Avro.Deriving.NormSchema
+import           Data.Avro.EitherN
 
-import Data.Avro.Deriving.NormSchema
-import Data.Avro.EitherN
 
-import qualified Data.ByteString            as BS
-import qualified Data.ByteString.Lazy       as LBS
-import qualified Data.ByteString.Lazy.Char8 as LBSC8
-import qualified Data.HashMap.Strict        as HM
-import qualified Data.Set                   as S
-import           Data.Text                  (Text)
-import qualified Data.Text                  as T
-import qualified Data.Vector                as V
-
-import Data.Avro.Deriving.Lift ()
-
 -- | How to treat Avro namespaces in the generated Haskell types.
 data NamespaceBehavior =
     IgnoreNamespaces
@@ -103,7 +91,7 @@
     -- @Com'example'Foo@. If @Foo@ had a field called @bar@, the
     -- generated Haskell record would have the field
     -- @com'example'FooBar@.
-  | Custom (T.Text -> [T.Text] -> T.Text)
+  | Custom (Text -> [Text] -> Text)
     -- ^ Provide a custom mapping from the name of the Avro type and
     -- its namespace that will be used to generate Haskell types and
     -- fields.
@@ -127,7 +115,7 @@
   { -- | How to build field names for generated data types. The first
     -- argument is the type name to use as a prefix, rendered
     -- according to the 'namespaceBehavior' setting.
-    fieldNameBuilder    :: Text -> Field -> T.Text
+    fieldNameBuilder    :: Text -> Field -> Text
 
     -- | Determines field representation of generated data types
   , fieldRepresentation :: TypeName -> Field -> (FieldStrictness, FieldUnpackedness)
@@ -146,6 +134,7 @@
 --   , namespaceBehavior = 'IgnoreNamespaces'
 --   }
 -- @
+defaultDeriveOptions :: DeriveOptions
 defaultDeriveOptions = DeriveOptions
   { fieldNameBuilder    = mkPrefixedFieldName
   , fieldRepresentation = mkLazyField
@@ -160,9 +149,9 @@
 -- @
 -- Person { personFirstName :: Text }
 -- @
-mkPrefixedFieldName :: Text -> Field -> T.Text
+mkPrefixedFieldName :: Text -> Field -> Text
 mkPrefixedFieldName prefix fld =
-  sanitiseName $ updateFirst T.toLower prefix <> updateFirst T.toUpper (fldName fld)
+  sanitiseName $ updateFirst Text.toLower prefix <> updateFirst Text.toUpper (Schema.fldName fld)
 
 -- | Marks any field as non-strict in the generated data types.
 mkLazyField :: TypeName -> Field -> (FieldStrictness, FieldUnpackedness)
@@ -180,19 +169,19 @@
   else (LazyField, NonUnpackedField)
   where
     unpackedness =
-      case S.fldType field of
-        S.Null    -> NonUnpackedField
-        S.Boolean -> NonUnpackedField
+      case Schema.fldType field of
+        Schema.Null    -> NonUnpackedField
+        Schema.Boolean -> NonUnpackedField
         _         -> UnpackedField
 
     shouldStricten =
-      case S.fldType field of
-        S.Null    -> True
-        S.Boolean -> True
-        S.Int _   -> True
-        S.Long _  -> True
-        S.Float   -> True
-        S.Double  -> True
+      case Schema.fldType field of
+        Schema.Null    -> True
+        Schema.Boolean -> True
+        Schema.Int _   -> True
+        Schema.Long _  -> True
+        Schema.Float   -> True
+        Schema.Double  -> True
         _         -> False
 
 -- | Generates a field name that matches the field name in schema
@@ -206,7 +195,7 @@
 -- @
 -- You may want to enable 'DuplicateRecordFields' if you want to use this method.
 mkAsIsFieldName :: Text -> Field -> Text
-mkAsIsFieldName _ = sanitiseName . updateFirst T.toLower . fldName
+mkAsIsFieldName _ = sanitiseName . updateFirst Text.toLower . Schema.fldName
 
 -- | Derives Haskell types from the given Avro schema file. These
 -- Haskell types support both reading and writing to Avro.
@@ -273,7 +262,7 @@
 deriveAvro' = deriveAvroWithOptions' defaultDeriveOptions
 
 -- | Same as 'deriveAvro' but takes a ByteString rather than FilePath
-deriveAvroFromByteString :: LBS.ByteString -> Q [Dec]
+deriveAvroFromByteString :: Lazy.ByteString -> Q [Dec]
 deriveAvroFromByteString bs = case eitherDecode bs of
     Right schema -> deriveAvroWithOptions' defaultDeriveOptions schema
     Left err     -> fail $ "Unable to generate Avro from bytestring: " <> err
@@ -288,7 +277,7 @@
 makeSchema :: FilePath -> Q Exp
 makeSchema p = readSchema p >>= lift
 
-makeSchemaFromByteString :: LBS.ByteString -> Q Exp
+makeSchemaFromByteString :: Lazy.ByteString -> Q Exp
 makeSchemaFromByteString bs = case eitherDecode @Schema bs of
   Right schema -> lift schema
   Left err     -> fail $ "Unable to generate Avro Schema from bytestring: " <> err
@@ -296,9 +285,8 @@
 makeSchemaFrom :: FilePath -> Text -> Q Exp
 makeSchemaFrom p name = do
   s <- readSchema p
-
-  case subdefinition s name of
-    Nothing -> fail $ "No such entity '" <> T.unpack name <> "' defined in " <> p
+  case Schema.subdefinition s name of
+    Nothing -> fail $ "No such entity '" <> Text.unpack name <> "' defined in " <> p
     Just ss -> lift ss
 
 readSchema :: FilePath -> Q Schema
@@ -315,29 +303,29 @@
 badValueNew v t = Left $ "Unexpected value for '" <> t <> "': " <> show v
 
 genFromValue :: NamespaceBehavior -> Schema -> Q [Dec]
-genFromValue namespaceBehavior (S.Enum n _ _ _ ) =
-  [d| instance AV.FromAvro $(conT $ mkDataTypeName namespaceBehavior n) where
+genFromValue namespaceBehavior (Schema.Enum n _ _ _ ) =
+  [d| instance AV.FromAvro $(TH.conT $ mkDataTypeName namespaceBehavior n) where
         fromAvro (AV.Enum _ i _) = $([| pure . toEnum|]) i
-        fromAvro value           = $( [|\v -> badValueNew v $(mkTextLit $ S.renderFullname n)|] ) value
+        fromAvro value           = $( [|\v -> badValueNew v $(mkTextLit $ Schema.renderFullname n)|] ) value
   |]
-genFromValue namespaceBehavior (S.Record n _ _ fs) =
-  [d| instance AV.FromAvro $(conT $ mkDataTypeName namespaceBehavior n) where
+genFromValue namespaceBehavior (Schema.Record n _ _ fs) =
+  [d| instance AV.FromAvro $(TH.conT $ mkDataTypeName namespaceBehavior n) where
         fromAvro (AV.Record _ r) =
            $(genFromAvroNewFieldsExp (mkDataTypeName namespaceBehavior n) fs) r
-        fromAvro value           = $( [|\v -> badValueNew v $(mkTextLit $ S.renderFullname n)|] ) value
+        fromAvro value           = $( [|\v -> badValueNew v $(mkTextLit $ Schema.renderFullname n)|] ) value
   |]
-genFromValue namespaceBehavior (S.Fixed n _ s _) =
-  [d| instance AV.FromAvro $(conT $ mkDataTypeName namespaceBehavior n) where
+genFromValue namespaceBehavior (Schema.Fixed n _ s _) =
+  [d| instance AV.FromAvro $(TH.conT $ mkDataTypeName namespaceBehavior n) where
         fromAvro (AV.Fixed _ v)
-          | BS.length v == s = pure $ $(conE (mkDataTypeName namespaceBehavior n)) v
-        fromAvro value = $( [|\v -> badValueNew v $(mkTextLit $ S.renderFullname n)|] ) value
+          | Strict.length v == s = pure $ $(TH.conE (mkDataTypeName namespaceBehavior n)) v
+        fromAvro value = $( [|\v -> badValueNew v $(mkTextLit $ Schema.renderFullname n)|] ) value
   |]
 genFromValue _ _                             = pure []
 
 genFromAvroNewFieldsExp :: Name -> [Field] -> Q Exp
 genFromAvroNewFieldsExp n xs =
   [| \r ->
-    $(let ctor = [| pure $(conE n) |]
+    $(let ctor = [| pure $(TH.conE n) |]
       in foldl (\expr (i, _) -> [| $expr <*> AV.fromAvro (r V.! i) |]) ctor (zip [(0 :: Int)..] xs)
     )
   |]
@@ -346,14 +334,14 @@
 
 genHasAvroSchema :: NamespaceBehavior -> Schema -> Q [Dec]
 genHasAvroSchema namespaceBehavior s = do
-  let sname = mkSchemaValueName namespaceBehavior (name s)
+  let sname = mkSchemaValueName namespaceBehavior (Schema.name s)
   sdef <- schemaDef sname s
   idef <- hasAvroSchema sname
   pure (sdef <> idef)
   where
     hasAvroSchema sname =
-      [d| instance HasAvroSchema $(conT $ mkDataTypeName namespaceBehavior (name s)) where
-            schema = pure $(varE sname)
+      [d| instance HasAvroSchema $(TH.conT $ mkDataTypeName namespaceBehavior (Schema.name s)) where
+            schema = pure $(TH.varE sname)
       |]
 
 newNames :: String
@@ -366,40 +354,40 @@
 ------------------------- ToAvro ------------------------------------------------
 
 genToAvro :: DeriveOptions -> Schema -> Q [Dec]
-genToAvro opts s@(S.Enum n _ _ _) =
+genToAvro opts (Schema.Enum n _ _ _) =
   encodeAvroInstance (mkSchemaValueName (namespaceBehavior opts) n)
   where
-    encodeAvroInstance sname =
-      [d| instance ToAvro $(conT $ mkDataTypeName (namespaceBehavior opts) n) where
+    encodeAvroInstance _ =
+      [d| instance ToAvro $(TH.conT $ mkDataTypeName (namespaceBehavior opts) n) where
             toAvro = $([| \_ x -> putI (fromEnum x) |])
       |]
 
-genToAvro opts s@(S.Record n _ _ fs) =
+genToAvro opts (Schema.Record n _ _ fs) =
   encodeAvroInstance (mkSchemaValueName (namespaceBehavior opts) n)
   where
     encodeAvroInstance sname =
-      [d| instance ToAvro $(conT $ mkDataTypeName (namespaceBehavior opts) n) where
+      [d| instance ToAvro $(TH.conT $ mkDataTypeName (namespaceBehavior opts) n) where
             toAvro = $(encodeAvroFieldsExp sname)
       |]
-    encodeAvroFieldsExp sname = do
+    encodeAvroFieldsExp _ = do
       names <- newNames "p_" (length fs)
-      wn <- varP <$> newName "_"
-      let con = conP (mkDataTypeName (namespaceBehavior opts) n) (varP <$> names)
-      lamE [wn, con]
-            [| mconcat $( let build (fld, n) = [| toAvro (fldType fld) $(varE n) |]
-                          in listE $ build <$> zip fs names
+      wn <- TH.varP <$> newName "_"
+      let con = TH.conP (mkDataTypeName (namespaceBehavior opts) n) (TH.varP <$> names)
+      TH.lamE [wn, con]
+            [| mconcat $( let build (fld, nm) = [| toAvro (Schema.fldType fld) $(TH.varE nm) |]
+                          in TH.listE $ build <$> zip fs names
                         )
             |]
 
-genToAvro opts s@(S.Fixed n _ _ _) =
+genToAvro opts (Schema.Fixed n _ _ _) =
   encodeAvroInstance (mkSchemaValueName (namespaceBehavior opts) n)
   where
     encodeAvroInstance sname =
-      [d| instance ToAvro $(conT $ mkDataTypeName (namespaceBehavior opts) n) where
+      [d| instance ToAvro $(TH.conT $ mkDataTypeName (namespaceBehavior opts) n) where
             toAvro = $(do
               x <- newName "x"
               wc <- newName "_"
-              lamE [varP wc, conP (mkDataTypeName (namespaceBehavior opts) n) [varP x]] [| toAvro $(varE sname) $(varE x) |])
+              TH.lamE [TH.varP wc, TH.conP (mkDataTypeName (namespaceBehavior opts) n) [TH.varP x]] [| toAvro $(TH.varE sname) $(TH.varE x) |])
       |]
 genToAvro _ _ = pure []
 
@@ -421,73 +409,98 @@
     sn _ d                   = d
 
 genType :: DeriveOptions -> Schema -> Q [Dec]
-genType opts (S.Record n _ _ fs) = do
+genType opts (Schema.Record n _ _ fs) = do
   flds <- traverse (mkField opts n) fs
   let dname = mkDataTypeName (namespaceBehavior opts) n
   sequenceA [genDataType dname flds]
-genType opts (S.Enum n _ _ vs) = do
+genType opts (Schema.Enum n _ _ vs) = do
   let dname = mkDataTypeName (namespaceBehavior opts) n
   sequenceA [genEnum dname (mkAdtCtorName (namespaceBehavior opts) n <$> V.toList vs)]
-genType opts (S.Fixed n _ s _) = do
+genType opts (Schema.Fixed n _ _ _) = do
   let dname = mkDataTypeName (namespaceBehavior opts) n
   sequenceA [genNewtype dname]
 genType _ _ = pure []
 
-mkFieldTypeName :: NamespaceBehavior -> S.Schema -> Q TH.Type
+mkFieldTypeName :: NamespaceBehavior -> Schema -> Q TH.Type
 mkFieldTypeName namespaceBehavior = \case
-  S.Null             -> [t| () |]
-  S.Boolean          -> [t| Bool |]
-  S.Long (Just (DecimalL (Decimal p s)))
-                     -> [t| Decimal $(litT $ numTyLit p) $(litT $ numTyLit s) |]
-  S.Long (Just TimeMicros)
-                     -> [t| DiffTime |]
-  S.Long (Just TimestampMicros)
-                     -> [t| UTCTime |]
-  S.Long (Just TimestampMillis)
-                     -> [t| UTCTime |]
-  S.Long _           -> [t| Int64 |]
-  S.Int (Just Date)  -> [t| Day |]
-  S.Int (Just TimeMillis)
-                     -> [t| DiffTime |]
-  S.Int _            -> [t| Int32 |]
-  S.Float            -> [t| Float |]
-  S.Double           -> [t| Double |]
-  S.Bytes _          -> [t| ByteString |]
-  S.String Nothing   -> [t| Text |]
-  S.String (Just UUID) -> [t| UUID |]
-  S.Union branches   -> union (Foldable.toList branches)
-  S.Record n _ _ _   -> [t| $(conT $ mkDataTypeName namespaceBehavior n) |]
-  S.Map x            -> [t| Map Text $(go x) |]
-  S.Array x          -> [t| [$(go x)] |]
-  S.NamedType n      -> [t| $(conT $ mkDataTypeName namespaceBehavior n)|]
-  S.Fixed n _ _ _    -> [t| $(conT $ mkDataTypeName namespaceBehavior n)|]
-  S.Enum n _ _ _     -> [t| $(conT $ mkDataTypeName namespaceBehavior n)|]
-  where go = mkFieldTypeName namespaceBehavior
-        union = \case
-          []              ->
-            error "Empty union types are not supported"
-          [x]             -> [t| Identity $(go x) |]
-          [Null, x]       -> [t| Maybe $(go x) |]
-          [x, Null]       -> [t| Maybe $(go x) |]
-          [x, y]          -> [t| Either $(go x) $(go y) |]
-          [a, b, c]       -> [t| Either3 $(go a) $(go b) $(go c) |]
-          [a, b, c, d]    -> [t| Either4 $(go a) $(go b) $(go c) $(go d) |]
-          [a, b, c, d, e] -> [t| Either5 $(go a) $(go b) $(go c) $(go d) $(go e) |]
-          [a, b, c, d, e, f] -> [t| Either6 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) |]
-          [a, b, c, d, e, f, g] -> [t| Either7 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) $(go g)|]
-          [a, b, c, d, e, f, g, h] -> [t| Either8 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) $(go g) $(go h)|]
-          [a, b, c, d, e, f, g, h, i] -> [t| Either9 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) $(go g) $(go h) $(go i)|]
-          [a, b, c, d, e, f, g, h, i, j] -> [t| Either10 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) $(go g) $(go h) $(go i) $(go j)|]
-          ls              ->
-            error $ "Unions with more than 10 elements are not yet supported: Union has " <> (show . length) ls <> " elements"
+  Schema.Null             -> [t| () |]
+  Schema.Boolean          -> [t| Bool |]
 
+  Schema.Long (Just (Schema.DecimalL (Schema.Decimal p s)))
+    -> [t| Schema.Decimal $(TH.litT $ TH.numTyLit p) $(TH.litT $ TH.numTyLit s) |]
+  Schema.Long (Just Schema.TimeMicros)
+    -> [t| DiffTime |]
+  Schema.Long (Just Schema.TimestampMicros)
+    -> [t| UTCTime |]
+  Schema.Long (Just Schema.TimestampMillis)
+    -> [t| UTCTime |]
+  Schema.Long (Just Schema.LocalTimestampMillis)
+    -> [t| LocalTime |]
+  Schema.Long (Just Schema.LocalTimestampMicros)
+    -> [t| LocalTime |]
+  Schema.Long Nothing
+    -> [t| Int64 |]
+
+  Schema.Int (Just Schema.Date)
+    -> [t| Day |]
+  Schema.Int (Just Schema.TimeMillis)
+    -> [t| DiffTime |]
+  Schema.Int _
+    -> [t| Int32 |]
+  Schema.Float
+    -> [t| Float |]
+  Schema.Double
+    -> [t| Double |]
+  Schema.Bytes _
+    -> [t| ByteString |]
+  Schema.String Nothing
+    -> [t| Text |]
+  Schema.String (Just Schema.UUID) ->
+    [t| UUID |]
+  Schema.Union branches
+    -> union (Foldable.toList branches)
+  Schema.Record n _ _ _
+    -> [t| $(TH.conT $ mkDataTypeName namespaceBehavior n) |]
+  Schema.Map x
+    -> [t| Map Text $(go x) |]
+  Schema.Array x
+    -> [t| [$(go x)] |]
+  Schema.NamedType n
+    -> [t| $(TH.conT $ mkDataTypeName namespaceBehavior n)|]
+  Schema.Fixed n _ _ _
+    -> [t| $(TH.conT $ mkDataTypeName namespaceBehavior n)|]
+  Schema.Enum n _ _ _
+    -> [t| $(TH.conT $ mkDataTypeName namespaceBehavior n)|]
+  where
+    go = mkFieldTypeName namespaceBehavior
+    union = \case
+      []
+        -> error "Empty union types are not supported"
+      [x]
+        -> [t| Identity $(go x) |]
+      [Schema.Null, x]
+        -> [t| Maybe $(go x) |]
+      [x, Schema.Null]
+        -> [t| Maybe $(go x) |]
+      [x, y] -> [t| Either $(go x) $(go y) |]
+      [a, b, c] -> [t| Either3 $(go a) $(go b) $(go c) |]
+      [a, b, c, d] -> [t| Either4 $(go a) $(go b) $(go c) $(go d) |]
+      [a, b, c, d, e] -> [t| Either5 $(go a) $(go b) $(go c) $(go d) $(go e) |]
+      [a, b, c, d, e, f] -> [t| Either6 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) |]
+      [a, b, c, d, e, f, g] -> [t| Either7 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) $(go g)|]
+      [a, b, c, d, e, f, g, h] -> [t| Either8 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) $(go g) $(go h)|]
+      [a, b, c, d, e, f, g, h, i] -> [t| Either9 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) $(go g) $(go h) $(go i)|]
+      [a, b, c, d, e, f, g, h, i, j] -> [t| Either10 $(go a) $(go b) $(go c) $(go d) $(go e) $(go f) $(go g) $(go h) $(go i) $(go j)|]
+      ls              ->
+        error $ "Unions with more than 10 elements are not yet supported: Union has " <> (show . length) ls <> " elements"
+
 updateFirst :: (Text -> Text) -> Text -> Text
 updateFirst f t =
-  let (l, ls) = T.splitAt 1 t
+  let (l, ls) = Text.splitAt 1 t
   in f l <> ls
 
 decodeSchema :: FilePath -> IO (Either String Schema)
-decodeSchema p = eitherDecode <$> LBS.readFile p
+decodeSchema p = eitherDecode <$> Lazy.readFile p
 
 mkAdtCtorName :: NamespaceBehavior -> TypeName -> Text -> Name
 mkAdtCtorName namespaceBehavior prefix nm =
@@ -499,7 +512,7 @@
 sanitiseName :: Text -> Text
 sanitiseName =
   let valid c = isAlphaNum c || c == '\'' || c == '_'
-  in T.concat . T.split (not . valid)
+  in Text.concat . Text.split (not . valid)
 
 -- | Renders a fully qualified Avro name to a valid Haskell
 -- identifier. This does not change capitalization—make sure to
@@ -520,7 +533,7 @@
               -- ^ The name to transform into a valid Haskell
               -- identifier.
            -> Text
-renderName namespaceBehavior (TN name namespace) = case namespaceBehavior of
+renderName namespaceBehavior (Schema.TN name namespace) = case namespaceBehavior of
   HandleNamespaces -> Text.intercalate "'" $ namespace <> [name]
   IgnoreNamespaces -> name
   Custom f         -> f name namespace
@@ -534,11 +547,11 @@
 
 mkDataTypeName' :: Text -> Name
 mkDataTypeName' =
-  mkTextName . sanitiseName . updateFirst T.toUpper . T.takeWhileEnd (/='.')
+  mkTextName . sanitiseName . updateFirst Text.toUpper . Text.takeWhileEnd (/='.')
 
 mkField :: DeriveOptions -> TypeName -> Field -> Q VarStrictType
 mkField opts typeName field = do
-  ftype <- mkFieldTypeName (namespaceBehavior opts) (fldType field)
+  ftype <- mkFieldTypeName (namespaceBehavior opts) (Schema.fldType field)
   let prefix = renderName (namespaceBehavior opts) typeName
       fName = mkTextName $ fieldNameBuilder opts prefix field
       (fieldStrictness, fieldUnpackedness) =
@@ -618,10 +631,7 @@
 #endif
 
 mkTextName :: Text -> Name
-mkTextName = mkName . T.unpack
-
-mkLit :: String -> ExpQ
-mkLit = litE . StringL
+mkTextName = mkName . Text.unpack
 
-mkTextLit :: Text -> ExpQ
-mkTextLit = litE . StringL . T.unpack
+mkTextLit :: Text -> TH.ExpQ
+mkTextLit = TH.litE . StringL . Text.unpack
diff --git a/src/Data/Avro/Deriving/Lift.hs b/src/Data/Avro/Deriving/Lift.hs
--- a/src/Data/Avro/Deriving/Lift.hs
+++ b/src/Data/Avro/Deriving/Lift.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP                #-}
 {-# LANGUAGE DeriveLift         #-}
 {-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE TemplateHaskell    #-}
@@ -8,26 +7,9 @@
 module Data.Avro.Deriving.Lift where
 
 import qualified Data.Avro.Schema.Schema    as Schema
-import qualified Data.ByteString            as ByteString
-import qualified Data.HashMap.Strict        as HashMap
-import qualified Data.Text                  as Text
-import qualified Data.Vector                as Vector
-import           Language.Haskell.TH.Syntax (Lift (..))
 
-instance Lift ByteString.ByteString where
-  lift b = [| ByteString.pack $(lift $ ByteString.unpack b) |]
-
-#if MIN_VERSION_text(1,2,4)
-#else
-instance Lift Text.Text where
-  lift t = [| Text.pack $(lift $ Text.unpack t) |]
-#endif
-
-instance Lift a => Lift (Vector.Vector a) where
-  lift v = [| Vector.fromList $(lift $ Vector.toList v) |]
-
-instance (Lift k, Lift v) => Lift (HashMap.HashMap k v) where
-  lift m = [| HashMap.fromList $(lift $ HashMap.toList m) |]
+import           Language.Haskell.TH.Syntax (Lift (..))
+import           Instances.TH.Lift ()
 
 deriving instance Lift Schema.DefaultValue
 deriving instance Lift Schema.Field
diff --git a/src/Data/Avro/Deriving/NormSchema.hs b/src/Data/Avro/Deriving/NormSchema.hs
--- a/src/Data/Avro/Deriving/NormSchema.hs
+++ b/src/Data/Avro/Deriving/NormSchema.hs
@@ -6,15 +6,7 @@
 import           Control.Monad.State.Strict
 import           Data.Avro.Schema.Schema
 import qualified Data.Foldable              as Foldable
-import qualified Data.List                  as L
-import           Data.List.NonEmpty         (NonEmpty ((:|)))
 import qualified Data.Map.Strict            as M
-import           Data.Maybe                 (catMaybes, fromMaybe)
-import           Data.Semigroup             ((<>))
-import qualified Data.Set                   as S
-import           Data.Text                  (Text)
-import qualified Data.Text                  as T
-import qualified Data.Vector                as V
 
 -- | Extracts all the records from the schema (flattens the schema)
 -- Named types get resolved when needed to include at least one "inlined"
@@ -23,10 +15,10 @@
 -- namespaces (including inlined into full names) will be ignored
 -- during names resolution.
 extractDerivables :: Schema -> [Schema]
-extractDerivables s = flip evalState state . normSchema . snd <$> rawRecs
+extractDerivables s = flip evalState initial . normSchema . snd <$> rawRecs
   where
     rawRecs = getTypes s
-    state = M.fromList rawRecs
+    initial = M.fromList rawRecs
 
 getTypes :: Schema -> [(TypeName, Schema)]
 getTypes rec = case rec of
@@ -49,7 +41,9 @@
         -- use the looked up schema (which might be a full record) and replace
         -- it in the state with NamedType for future resolves
         -- because only one full definition per schema is needed
-        modify' (M.insert tn t) >> pure rs
+        modify' (M.insert tn t) >> case rs of
+            NamedType _ -> pure rs -- If we get a reference, the schema was already normalised.
+            _ -> normSchema rs -- Otherwise, normalise the schema before inlining.
 
         -- NamedType but no corresponding record?! Baaad!
       Nothing ->
@@ -58,12 +52,15 @@
   Array s -> Array <$> normSchema s
   Map s   -> Map <$> normSchema s
   Union l -> Union <$> traverse normSchema l
-  r@Record{name = tn}  -> do
-    modify' (M.insert tn (NamedType tn))
+  Record { name }  -> do
+    modify' (M.insert name (NamedType name))
     flds <- mapM (\fld -> setType fld <$> normSchema (fldType fld)) (fields r)
     pure $ r { fields = flds }
-  r@Fixed{name = tn} -> do
-    modify' (M.insert tn (NamedType tn))
+  Fixed { name } -> do
+    modify' (M.insert name (NamedType name))
+    pure r
+  Enum { name } -> do
+    modify' (M.insert name (NamedType name))
     pure r
   s         -> pure s
   where
diff --git a/src/Data/Avro/EitherN.hs b/src/Data/Avro/EitherN.hs
--- a/src/Data/Avro/EitherN.hs
+++ b/src/Data/Avro/EitherN.hs
@@ -443,6 +443,7 @@
   fromAvro (AV.Union _ 1 b) = E3_2 <$> fromAvro b
   fromAvro (AV.Union _ 2 c) = E3_3 <$> fromAvro c
   fromAvro (AV.Union _ n _) = Left ("Unable to decode Either3 from a position #" <> show n)
+  fromAvro n                = Left ("Unable to decode Either3 from value " <> AV.describeValue n)
 
 instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d) => FromAvro (Either4 a b c d) where
   fromAvro (AV.Union _ 0 a) = E4_1 <$> fromAvro a
@@ -450,6 +451,7 @@
   fromAvro (AV.Union _ 2 c) = E4_3 <$> fromAvro c
   fromAvro (AV.Union _ 3 d) = E4_4 <$> fromAvro d
   fromAvro (AV.Union _ n _) = Left ("Unable to decode Either4 from a position #" <> show n)
+  fromAvro n                = Left ("Unable to decode Either4 from value " <> AV.describeValue n)
 
 instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d, FromAvro e) => FromAvro (Either5 a b c d e) where
   fromAvro (AV.Union _ 0 a) = E5_1 <$> fromAvro a
@@ -458,6 +460,7 @@
   fromAvro (AV.Union _ 3 d) = E5_4 <$> fromAvro d
   fromAvro (AV.Union _ 4 e) = E5_5 <$> fromAvro e
   fromAvro (AV.Union _ n _) = Left ("Unable to decode Either5 from a position #" <> show n)
+  fromAvro n                = Left ("Unable to decode Either5 from value " <> AV.describeValue n)
 
 instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d, FromAvro e, FromAvro f) => FromAvro (Either6 a b c d e f) where
   fromAvro (AV.Union _ 0 a) = E6_1 <$> fromAvro a
@@ -467,6 +470,7 @@
   fromAvro (AV.Union _ 4 e) = E6_5 <$> fromAvro e
   fromAvro (AV.Union _ 5 f) = E6_6 <$> fromAvro f
   fromAvro (AV.Union _ n _) = Left ("Unable to decode Either6 from a position #" <> show n)
+  fromAvro n                = Left ("Unable to decode Either6 from value " <> AV.describeValue n)
 
 instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d, FromAvro e, FromAvro f, FromAvro g) => FromAvro (Either7 a b c d e f g) where
   fromAvro (AV.Union _ 0 a) = E7_1 <$> fromAvro a
@@ -477,6 +481,7 @@
   fromAvro (AV.Union _ 5 f) = E7_6 <$> fromAvro f
   fromAvro (AV.Union _ 6 g) = E7_7 <$> fromAvro g
   fromAvro (AV.Union _ n _) = Left ("Unable to decode Either7 from a position #" <> show n)
+  fromAvro n                = Left ("Unable to decode Either7 from value " <> AV.describeValue n)
 
 instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d, FromAvro e, FromAvro f, FromAvro g, FromAvro h) => FromAvro (Either8 a b c d e f g h) where
   fromAvro (AV.Union _ 0 a) = E8_1 <$> fromAvro a
@@ -488,6 +493,7 @@
   fromAvro (AV.Union _ 6 g) = E8_7 <$> fromAvro g
   fromAvro (AV.Union _ 7 h) = E8_8 <$> fromAvro h
   fromAvro (AV.Union _ n _) = Left ("Unable to decode Either8 from a position #" <> show n)
+  fromAvro n                = Left ("Unable to decode Either8 from value " <> AV.describeValue n)
 
 instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d, FromAvro e, FromAvro f, FromAvro g, FromAvro h, FromAvro i) => FromAvro (Either9 a b c d e f g h i) where
   fromAvro (AV.Union _ 0 a) = E9_1 <$> fromAvro a
@@ -500,6 +506,7 @@
   fromAvro (AV.Union _ 7 h) = E9_8 <$> fromAvro h
   fromAvro (AV.Union _ 8 i) = E9_9 <$> fromAvro i
   fromAvro (AV.Union _ n _) = Left ("Unable to decode Either9 from a position #" <> show n)
+  fromAvro n                = Left ("Unable to decode Either9 from value " <> AV.describeValue n)
 
 instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d, FromAvro e, FromAvro f, FromAvro g, FromAvro h, FromAvro i, FromAvro j) => FromAvro (Either10 a b c d e f g h i j) where
   fromAvro (AV.Union _ 0 a) = E10_1  <$> fromAvro a
@@ -513,6 +520,7 @@
   fromAvro (AV.Union _ 8 i) = E10_9  <$> fromAvro i
   fromAvro (AV.Union _ 9 j) = E10_10 <$> fromAvro j
   fromAvro (AV.Union _ n _) = Left ("Unable to decode Either10 from a position #" <> show n)
+  fromAvro n                = Left ("Unable to decode Either10 from value " <> AV.describeValue n)
 
 putIndexedValue :: ToAvro a => Int -> V.Vector Schema -> a -> Builder
 putIndexedValue i opts x = putI i <> toAvro (V.unsafeIndex opts i) x
diff --git a/src/Data/Avro/Encoding/FromAvro.hs b/src/Data/Avro/Encoding/FromAvro.hs
--- a/src/Data/Avro/Encoding/FromAvro.hs
+++ b/src/Data/Avro/Encoding/FromAvro.hs
@@ -3,40 +3,34 @@
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE LambdaCase        #-}
 {-# LANGUAGE StrictData        #-}
-{-# LANGUAGE TupleSections     #-}
 module Data.Avro.Encoding.FromAvro
 ( FromAvro(..)
   -- ** For internal use
 , Value(..)
 , getValue
+
+, describeValue
 )
 where
 
 import           Control.DeepSeq             (NFData)
-import           Control.Monad               (forM, replicateM)
+import           Control.Monad               (forM, replicateM, void, when)
 import           Control.Monad.Identity      (Identity (..))
-import           Control.Monad.ST            (ST)
-import qualified Data.Aeson                  as A
 import qualified Data.Avro.Internal.Get      as Get
 import           Data.Avro.Internal.Time
 import           Data.Avro.Schema.Decimal    as D
 import           Data.Avro.Schema.ReadSchema (ReadSchema)
 import qualified Data.Avro.Schema.ReadSchema as ReadSchema
 import qualified Data.Avro.Schema.Schema     as Schema
-import           Data.Binary.Get             (Get, getByteString, runGetOrFail)
+import           Data.Binary.Get             (Get, getByteString)
 import qualified Data.ByteString             as BS
-import qualified Data.ByteString             as B
 import qualified Data.ByteString.Lazy        as BL
-import qualified Data.Char                   as Char
+import           Data.Foldable               (traverse_)
 import           Data.HashMap.Strict         (HashMap)
 import qualified Data.HashMap.Strict         as HashMap
 import           Data.Int
-import           Data.List.NonEmpty          (NonEmpty)
-import           Data.Foldable               (traverse_)
 import qualified Data.Map                    as Map
 import           Data.Text                   (Text)
-import qualified Data.Text                   as Text
-import qualified Data.Text                   as T
 import qualified Data.Text.Encoding          as Text
 import qualified Data.Time                   as Time
 import qualified Data.UUID                   as UUID
@@ -76,20 +70,20 @@
 -- (i.e. do not print values)
 describeValue :: Value -> String
 describeValue = \case
-  Null          -> "Null"
-  Boolean b     -> "Boolean"
-  Int s _       -> "Int (" <> show s <> ")"
-  Long s _      -> "Long (" <> show s <> ")"
-  Float s _     -> "Float (" <> show s <> ")"
-  Double s _    -> "Double (" <> show s <> ")"
-  Bytes s _     -> "Bytes (" <> show s <> ")"
-  String s _    -> "String (" <> show s <> ")"
-  Union s ix _  -> "Union (position = " <> show ix <> ", schema = " <> show s <> ")"
-  Fixed s _     -> "Fixed (" <> show s <> ")"
-  Enum s ix _   -> "Enum (position = " <> show ix <> ", schema =" <> show s <> ")"
-  Array vs      -> "Array (length = " <> show (V.length vs) <> ")"
-  Map vs        -> "Map (length = " <> show (HashMap.size vs) <> ")"
-  Record s vs   -> "Record (name = " <> show (ReadSchema.name s) <> " fieldsNum = " <> show (V.length vs) <> ")"
+  Null         -> "Null"
+  Boolean _    -> "Boolean"
+  Int s _      -> "Int (" <> show s <> ")"
+  Long s _     -> "Long (" <> show s <> ")"
+  Float s _    -> "Float (" <> show s <> ")"
+  Double s _   -> "Double (" <> show s <> ")"
+  Bytes s _    -> "Bytes (" <> show s <> ")"
+  String s _   -> "String (" <> show s <> ")"
+  Union s ix _ -> "Union (position = " <> show ix <> ", schema = " <> show s <> ")"
+  Fixed s _    -> "Fixed (" <> show s <> ")"
+  Enum s ix _  -> "Enum (position = " <> show ix <> ", schema =" <> show s <> ")"
+  Array vs     -> "Array (length = " <> show (V.length vs) <> ")"
+  Map vs       -> "Map (length = " <> show (HashMap.size vs) <> ")"
+  Record s vs  -> "Record (name = " <> show (ReadSchema.name s) <> " fieldsNum = " <> show (V.length vs) <> ")"
 
 --------------------------------------------------------------------------
 
@@ -194,6 +188,14 @@
   fromAvro x                                                              = Left ("Unable to decode UTCTime from: " <> show (describeValue x))
   {-# INLINE fromAvro #-}
 
+instance FromAvro Time.LocalTime where
+  fromAvro (Long (ReadSchema.Long _ (Just ReadSchema.LocalTimestampMicros)) n) =
+    Right $ microsToLocalTime (toInteger n)
+  fromAvro (Long (ReadSchema.Long _ (Just ReadSchema.LocalTimestampMillis)) n) =
+    Right $ millisToLocalTime (toInteger n)
+  fromAvro x = Left ("Unable to decode LocalTime from: " <> show (describeValue x))
+  {-# INLINE fromAvro #-}
+
 instance FromAvro a => FromAvro [a] where
   fromAvro (Array vec) = mapM fromAvro $ V.toList vec
   fromAvro x           = Left ("Unable to decode Array from: " <> show (describeValue x))
@@ -298,21 +300,33 @@
     v <- getField env t
     pure $ Union sch ix v
 
+
+-- | Read a Map from blocks of KV pairs
 getKVBlocks :: HashMap Schema.TypeName ReadSchema -> ReadSchema -> Get [[(Text, Value)]]
 getKVBlocks env t = do
-  blockLength <- abs <$> Get.getLong
-  if blockLength == 0
-  then return []
-  else do vs <- replicateM (fromIntegral blockLength) ((,) <$> Get.getString <*> getField env t)
-          (vs:) <$> getKVBlocks env t
+  lengthIndicator <- Get.getLong
+  if lengthIndicator == 0 then
+    return []
+  else do
+    -- When the block's count is negative, its absolute value is used, and the count is followed immediately by a
+    -- long block size indicating the number of bytes in the block.
+    when (lengthIndicator < 0) $ void Get.getLong -- number of bytes in block (ignored)
+    let blockLength = abs lengthIndicator
+    vs <- replicateM (fromIntegral blockLength) ((,) <$> Get.getString <*> getField env t)
+    (vs:) <$> getKVBlocks env t
 {-# INLINE getKVBlocks #-}
 
+-- | Read an array from blocks.
 getBlocksOf :: HashMap Schema.TypeName ReadSchema -> ReadSchema -> Get [[Value]]
 getBlocksOf env t = do
-  blockLength <- abs <$> Get.getLong
-  if blockLength == 0
-  then return []
+  lengthIndicator <- Get.getLong
+  if lengthIndicator == 0 then
+    return []
   else do
+    -- When the block's count is negative, its absolute value is used, and the count is followed immediately by a
+    -- long block size indicating the number of bytes in the block.
+    when (lengthIndicator < 0) $ void Get.getLong -- number of bytes in block (ignored)
+    let blockLength = abs lengthIndicator
     vs <- replicateM (fromIntegral blockLength) (getField env t)
     (vs:) <$> getBlocksOf env t
 
@@ -321,7 +335,7 @@
   moos <- fmap concat . forM fs $ \f ->
     case ReadSchema.fldStatus f of
       ReadSchema.Ignored       -> [] <$ getField env (ReadSchema.fldType f)
-      ReadSchema.AsIs i        -> (\f -> [(i,f)]) <$> getField env (ReadSchema.fldType f)
+      ReadSchema.AsIs i        -> (\fld -> [(i,fld)]) <$> getField env (ReadSchema.fldType f)
       ReadSchema.Defaulted i v -> pure [(i, convertValue v)] --undefined
 
   return $ V.create $ do
diff --git a/src/Data/Avro/Encoding/ToAvro.hs b/src/Data/Avro/Encoding/ToAvro.hs
--- a/src/Data/Avro/Encoding/ToAvro.hs
+++ b/src/Data/Avro/Encoding/ToAvro.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE ExistentialQuantification #-}
 {-# LANGUAGE FlexibleInstances         #-}
 {-# LANGUAGE OverloadedStrings         #-}
@@ -6,8 +7,7 @@
 {-# LANGUAGE TypeApplications          #-}
 {-# LANGUAGE TypeFamilies              #-}
 
-module Data.Avro.Encoding.ToAvro
-where
+module Data.Avro.Encoding.ToAvro where
 
 import           Control.Monad.Identity       (Identity (..))
 import qualified Data.Array                   as Ar
@@ -28,10 +28,11 @@
 import qualified Data.Map.Strict              as Map
 import           Data.Maybe                   (fromJust)
 import           Data.Text                    (Text)
-import qualified Data.Text                    as T
 import qualified Data.Text.Encoding           as T
+#if MIN_VERSION_text(2,0,0)
+import qualified Data.Text.Foreign            as T
+#endif
 import qualified Data.Text.Lazy               as TL
-import qualified Data.Text.Lazy.Encoding      as TL
 import qualified Data.Time                    as Time
 import qualified Data.UUID                    as UUID
 import qualified Data.Vector                  as V
@@ -48,7 +49,7 @@
 
 record :: Schema -> [(Text, Encoder)] -> Builder
 record (S.Record _ _ _ fs) vs =
-  foldMap (mapField provided) fs
+    foldMap (mapField provided) fs
   where
     provided :: HashMap Text Encoder
     provided = HashMap.fromList vs
@@ -61,6 +62,8 @@
     mapField :: HashMap Text Encoder -> S.Field -> Builder
     mapField env fld =
       maybe (failField fld) (flip runEncoder (S.fldType fld)) (HashMap.lookup (S.fldName fld) env)
+record _ _ =
+  error "Error in Schema passed to record. It was not of type Record."
 
 -- | Describes how to encode Haskell data types into Avro bytes
 class ToAvro a where
@@ -153,12 +156,21 @@
   toAvro s@(S.Long (Just S.TimestampMicros)) = toAvro @Int64 s . fromIntegral . diffTimeToMicros
   toAvro s@(S.Long (Just S.TimestampMillis)) = toAvro @Int64 s . fromIntegral . diffTimeToMillis
   toAvro s@(S.Int  (Just S.TimeMillis))      = toAvro @Int32 s . fromIntegral . diffTimeToMillis
-  toAvro s                                   = error ("Unble to decode DiffTime from " <> show s)
+  toAvro s                                   = error ("Unble to encode DiffTime as " <> show s)
 
 instance ToAvro Time.UTCTime where
   toAvro s@(S.Long (Just S.TimestampMicros)) = toAvro @Int64 s . fromIntegral . utcTimeToMicros
   toAvro s@(S.Long (Just S.TimestampMillis)) = toAvro @Int64 s . fromIntegral . utcTimeToMillis
+  toAvro s                                   = error ("Unable to encode UTCTime as " <> show s)
 
+instance ToAvro Time.LocalTime where
+  toAvro s@(S.Long (Just S.LocalTimestampMicros)) =
+    toAvro @Int64 s . fromIntegral . localTimeToMicros
+  toAvro s@(S.Long (Just S.LocalTimestampMillis)) =
+    toAvro @Int64 s . fromIntegral . localTimeToMillis
+  toAvro s =
+    error ("Unable to encode LocalTime as " <> show s)
+
 instance ToAvro B.ByteString where
   toAvro s bs = case s of
     (S.Bytes _)                        -> encodeRaw (B.length bs) <> byteString bs
@@ -174,13 +186,23 @@
 
 instance ToAvro Text where
   toAvro s v =
+#if MIN_VERSION_text(2,0,0)
     let
+      res =
+        encodeRaw @Int64 (fromIntegral (T.lengthWord8 v)) <> T.encodeUtf8Builder v
+     in case s of
+       (S.Bytes _)  -> res
+       (S.String _) -> res
+       _            -> error ("Unable to encode Text as: " <> show s)
+#else
+    let
       bs = T.encodeUtf8 v
       res = encodeRaw (B.length bs) <> byteString bs
     in case s of
       (S.Bytes _)  -> res
       (S.String _) -> res
       _            -> error ("Unable to encode Text as: " <> show s)
+#endif
   {-# INLINE toAvro #-}
 
 instance ToAvro TL.Text where
@@ -228,7 +250,7 @@
   toAvro s _ = error ("Unable to encode Maybe as " <> show s)
 
 instance (ToAvro a) => ToAvro (Identity a) where
-  toAvro (S.Union opts) e@(Identity a) =
+  toAvro (S.Union opts) (Identity a) =
     if V.length opts == 1
       then putI 0 <> toAvro (V.unsafeIndex opts 0) a
       else error ("Unable to encode Identity as a single-value union: " <> show opts)
diff --git a/src/Data/Avro/HasAvroSchema.hs b/src/Data/Avro/HasAvroSchema.hs
--- a/src/Data/Avro/HasAvroSchema.hs
+++ b/src/Data/Avro/HasAvroSchema.hs
@@ -8,9 +8,8 @@
 import qualified Data.Array               as Ar
 import           Data.Avro.Schema.Decimal as D
 import           Data.Avro.Schema.Schema  as S
-import qualified Data.ByteString          as B
-import           Data.ByteString.Lazy     (ByteString)
-import qualified Data.ByteString.Lazy     as BL
+import qualified Data.ByteString          as Strict
+import qualified Data.ByteString.Lazy     as Lazy
 import qualified Data.HashMap.Strict      as HashMap
 import           Data.Int
 import           Data.Ix                  (Ix)
@@ -81,10 +80,10 @@
 instance HasAvroSchema TL.Text where
   schema = Tagged S.String'
 
-instance HasAvroSchema B.ByteString where
+instance HasAvroSchema Strict.ByteString where
   schema = Tagged S.Bytes'
 
-instance HasAvroSchema BL.ByteString where
+instance HasAvroSchema Lazy.ByteString where
   schema = Tagged S.Bytes'
 
 instance (KnownNat p, KnownNat s) => HasAvroSchema (D.Decimal p s) where
@@ -103,6 +102,9 @@
 
 instance HasAvroSchema Time.UTCTime where
   schema = Tagged $ S.Long (Just TimestampMicros)
+
+instance HasAvroSchema Time.LocalTime where
+  schema = Tagged $ S.Long (Just LocalTimestampMicros)
 
 instance (HasAvroSchema a) => HasAvroSchema (Identity a) where
   schema = Tagged $ S.Union $ V.fromListN 1 [untag @a schema]
diff --git a/src/Data/Avro/Internal/Container.hs b/src/Data/Avro/Internal/Container.hs
--- a/src/Data/Avro/Internal/Container.hs
+++ b/src/Data/Avro/Internal/Container.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE LambdaCase          #-}
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE DeriveFunctor       #-}
 {-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE RankNTypes          #-}
 {-# LANGUAGE RecordWildCards     #-}
@@ -24,11 +25,9 @@
 import           Data.ByteString.Builder      (Builder, lazyByteString, toLazyByteString)
 import qualified Data.ByteString.Lazy         as BL
 import qualified Data.ByteString.Lazy.Char8   as BLC
-import           Data.Either                  (isRight)
 import           Data.HashMap.Strict          (HashMap)
 import qualified Data.HashMap.Strict          as HashMap
 import           Data.Int                     (Int32, Int64)
-import           Data.List                    (foldl', unfoldr)
 import qualified Data.Map.Strict              as Map
 import           Data.Text                    (Text)
 import           System.Random.TF.Init        (initTFGen)
@@ -94,51 +93,75 @@
 decodeRawBlocks :: BL.ByteString -> Either String (Schema, [Either String (Int, BL.ByteString)])
 decodeRawBlocks bs =
   case Get.runGetOrFail getContainerHeader bs of
-    Left (bs', _, err) -> Left err
-    Right (bs', _, ContainerHeader {..}) ->
-      let blocks = allBlocks syncBytes decompress bs'
+    Left (_, _, err) -> Left err
+    Right (bs', _, containerHeader@ContainerHeader {..}) ->
+      let blocks = allBlocks containerHeader bs'
       in Right (containedSchema, blocks)
   where
-    allBlocks sync decompress bytes =
-      flip unfoldr (Just bytes) $ \case
-        Just rest -> next sync decompress rest
-        Nothing   -> Nothing
+    allBlocks containerHeader bytes =
+      foldrBlocks (\x -> (Right x :)) (\err -> [Left err]) [] bytes
+        (decodeRawBlocksIncremental containerHeader)
 
-    next syncBytes decompress bytes =
-      case getNextBlock syncBytes decompress bytes of
-        Right (Just (numObj, block, rest)) -> Just (Right (numObj, block), Just rest)
-        Right Nothing                      -> Nothing
-        Left err                           -> Just (Left err, Nothing)
+data Blocks a
+  = Block
+      a
+      (Blocks a)
+  | More
+      (ByteString -> Blocks a) -- ^ Feed more bytes. Pass the empty ByteString to
+                               -- signal end of input.
+  | Error
+      String -- ^ Error message
+      ByteString -- ^ Leftover bytes
+  | Done
+      ByteString -- ^ Leftover bytes
+  deriving (Functor)
 
-getNextBlock :: BL.ByteString
-             -> Decompress BL.ByteString
-             -> BL.ByteString
-             -> Either String (Maybe (Int, BL.ByteString, BL.ByteString))
-getNextBlock sync decompress bs =
-  if BL.null bs
-    then Right Nothing
-    else case Get.runGetOrFail (getRawBlock decompress) bs of
-      Left (bs', _, err)             -> Left err
-      Right (bs', _, (nrObj, bytes)) ->
-        case checkMarker sync bs' of
-          Left err   -> Left err
-          Right rest -> Right $ Just (nrObj, bytes, rest)
+-- | Feeds a 'BL.ByteString' to the 'Blocks' until exhausted.
+-- Consumes the 'BL.ByteString' lazily.
+foldrBlocks :: (a -> b -> b) -> (String -> b) -> b -> BL.ByteString -> Blocks a -> b
+foldrBlocks block err done input = go (BL.toChunks input)
   where
-    getRawBlock :: Decompress BL.ByteString -> Get (Int, BL.ByteString)
-    getRawBlock decompress = do
-      nrObj    <- AGet.getLong >>= AGet.sFromIntegral
-      nrBytes  <- AGet.getLong
+    go chunks (Block a rest)     = block a (go chunks rest)
+    go []     (More cont)        = go [] (cont "")
+    go (c:cx) (More cont)        = go cx (cont c)
+    go _      (Error message _)  = err message
+    go _      (Done _)           = done
+
+decodeRawBlocksIncremental :: ContainerHeader -> Blocks (Int, BL.ByteString)
+decodeRawBlocksIncremental ContainerHeader{..} = initial
+  where
+    initialDecoder =
+      Get.runGetIncremental getRawBlock
+
+    initial = More $ \input ->
+      case input of
+        "" -> Done ""
+        _  -> go (Get.pushChunk initialDecoder input)
+
+    go decoder = case decoder of
+      Get.Done rest _ !block ->
+        case rest of
+          "" -> Block block initial
+          _  -> Block block (go (Get.pushChunk initialDecoder rest))
+      Get.Fail rest _ err ->
+        Error err rest
+      Get.Partial{} -> More $ \input ->
+        case input of
+          "" -> go (Get.pushEndOfInput decoder)
+          _  -> go (Get.pushChunk decoder input)
+
+    getRawBlock = do
+      nrObj   <- AGet.getLong >>= AGet.sFromIntegral
+      nrBytes <- AGet.getLong
       compressed <- Get.getLazyByteString nrBytes
       bytes <- case decompress compressed Get.getRemainingLazyByteString of
-        Right x  -> pure x
+        Right x -> pure x
         Left err -> fail err
-      pure (nrObj, bytes)
-
-    checkMarker :: BL.ByteString -> BL.ByteString -> Either String BL.ByteString
-    checkMarker sync bs =
-      case BL.splitAt nrSyncBytes bs of
-        (marker, _) | marker /= sync -> Left "Invalid marker, does not match sync bytes."
-        (_, rest)                    -> Right rest
+      trailer <- Get.getLazyByteString nrSyncBytes
+      if trailer /= syncBytes then
+        fail "Invalid marker, does not match sync bytes."
+      else
+        pure (nrObj, bytes)
 
 -- | Splits container into a list of individual avro-encoded values.
 -- This version provides both encoded and decoded values.
@@ -166,17 +189,30 @@
   -> BL.ByteString
   -> Either String (Schema, [Either String a])
 extractContainerValues deconflict f bs = do
-  (sch, blocks) <- decodeRawBlocks bs
-  readSchema <- deconflict sch
-  pure (sch, takeWhileInclusive isRight $ blocks >>= decodeBlock readSchema)
+  case Get.runGetOrFail getContainerHeader bs of
+    Left (_, _, err) -> Left err
+    Right (rest, _, containerHeader) -> do
+      (schema, blocks) <- extractContainerValuesIncremental deconflict f containerHeader
+      let values = foldrBlocks (++) (\err -> [Left err]) [] rest blocks
+      pure (schema, values)
+
+extractContainerValuesIncremental
+  :: (Schema -> Either String schema)
+  -> (schema -> Get a)
+  -> ContainerHeader
+  -> Either String (Schema, Blocks [Either String a])
+extractContainerValuesIncremental deconflict getValue containerHeader@ContainerHeader{..} = do
+  readSchema <- deconflict containedSchema
+  let blocks = decodeRawBlocksIncremental containerHeader
+  pure (containedSchema, fmap (decodeBlock readSchema) blocks)
   where
-    decodeBlock _ (Left err)               = undefined
-    decodeBlock sch (Right (nrObj, bytes)) = snd $ consumeN (fromIntegral nrObj) (decodeValue sch) bytes
+    decodeBlock readSchema (nrObj, bytes) =
+      snd $ consumeN (fromIntegral nrObj) (decodeValue readSchema) bytes
 
-    decodeValue sch bytes =
-      case Get.runGetOrFail (f sch) bytes of
-        Left (bs', _, err)  -> (bs', Left err)
-        Right (bs', _, res) -> (bs', Right res)
+    decodeValue readSchema bytes =
+      case Get.runGetOrFail (getValue readSchema) bytes of
+        Left (rest, _, err) -> (rest, Left err)
+        Right (rest, _, value) -> (rest, Right value)
 
 -- | Packs a container from a given list of already encoded Avro values
 -- Each bytestring should represent exactly one one value serialised to Avro.
diff --git a/src/Data/Avro/Internal/Get.hs b/src/Data/Avro/Internal/Get.hs
--- a/src/Data/Avro/Internal/Get.hs
+++ b/src/Data/Avro/Internal/Get.hs
@@ -11,24 +11,16 @@
 module Data.Avro.Internal.Get
 where
 
-import qualified Codec.Compression.Zlib     as Z
-import           Control.Monad              (replicateM, when)
+import           Control.Monad              (replicateM)
 import           Data.Binary.Get            (Get)
 import qualified Data.Binary.Get            as G
 import           Data.Binary.IEEE754        as IEEE
 import           Data.Bits
 import           Data.ByteString            (ByteString)
 import qualified Data.ByteString.Lazy       as BL
-import qualified Data.ByteString.Lazy.Char8 as BC
 import           Data.Int
-import qualified Data.Map                   as Map
-import           Data.Maybe
-import           Data.Monoid                ((<>))
-import qualified Data.Set                   as Set
 import           Data.Text                  (Text)
-import qualified Data.Text                  as Text
 import qualified Data.Text.Encoding         as Text
-import qualified Data.Vector                as V
 import           Prelude                    as P
 
 import Data.Avro.Internal.DecodeRaw
diff --git a/src/Data/Avro/Internal/Time.hs b/src/Data/Avro/Internal/Time.hs
--- a/src/Data/Avro/Internal/Time.hs
+++ b/src/Data/Avro/Internal/Time.hs
@@ -3,10 +3,8 @@
 
 -- Utility functions to work with times
 
-import Data.Fixed            (Fixed (..))
 import Data.Maybe            (fromJust)
 import Data.Time
-import Data.Time.Clock
 import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
 #if MIN_VERSION_time(1,9,0)
 import Data.Time.Format.Internal
@@ -50,3 +48,15 @@
 
 millisToUTCTime :: Integer -> UTCTime
 millisToUTCTime x = addUTCTime (realToFrac $ picosecondsToDiffTime (x * 1000000000)) epoch
+
+localTimeToMicros :: LocalTime -> Integer
+localTimeToMicros = utcTimeToMicros . localTimeToUTC utc
+
+localTimeToMillis :: LocalTime -> Integer
+localTimeToMillis = utcTimeToMillis . localTimeToUTC utc
+
+microsToLocalTime :: Integer -> LocalTime
+microsToLocalTime = utcToLocalTime utc . microsToUTCTime
+
+millisToLocalTime :: Integer -> LocalTime
+millisToLocalTime = utcToLocalTime utc . millisToUTCTime
diff --git a/src/Data/Avro/JSON.hs b/src/Data/Avro/JSON.hs
--- a/src/Data/Avro/JSON.hs
+++ b/src/Data/Avro/JSON.hs
@@ -60,6 +60,8 @@
 module Data.Avro.JSON where
 
 import qualified Data.Aeson           as Aeson
+import qualified Data.Aeson.Key       as K
+import qualified Data.Aeson.KeyMap    as KM
 import           Data.ByteString.Lazy (ByteString)
 import qualified Data.Foldable        as Foldable
 import           Data.HashMap.Strict  ((!))
@@ -99,12 +101,14 @@
               | isBuiltIn name = name
               | otherwise      = Schema.renderFullname $ Schema.parseFullname name
             branch =
-              head $ HashMap.keys obj
+              K.toText $ head (KM.keys obj)
             names =
               HashMap.fromList [(Schema.typeName t, t) | t <- Foldable.toList schemas]
           in case HashMap.lookup (canonicalize branch) names of
             Just t  -> do
-              nested <- parseAvroJSON union env t (obj ! branch)
+              nested <- parseAvroJSON union env t $ case KM.lookup (K.fromText branch) obj of
+                Just val -> val
+                Nothing -> error "impossible"
               return (Schema.DUnion schemas t nested)
             Nothing -> fail ("Type '" <> Text.unpack branch <> "' not in union: " <> show schemas)
     union Schema.Union{} _ =
diff --git a/src/Data/Avro/Schema/Decimal.hs b/src/Data/Avro/Schema/Decimal.hs
--- a/src/Data/Avro/Schema/Decimal.hs
+++ b/src/Data/Avro/Schema/Decimal.hs
@@ -3,7 +3,11 @@
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE PolyKinds                  #-}
 {-# LANGUAGE ScopedTypeVariables        #-}
-module Data.Avro.Schema.Decimal where
+module Data.Avro.Schema.Decimal
+( Decimal
+, fromUnderlyingValue
+, underlyingValue )
+where
 
 import qualified Data.BigDecimal as D
 import           Data.Proxy
@@ -13,11 +17,14 @@
   = Decimal { unDecimal :: D.BigDecimal }
   deriving (Eq, Ord, Show, Read, Num, Fractional, Real)
 
+intScale :: D.BigDecimal -> Integer
+intScale = toInteger . D.scale
+
 fromUnderlyingValue
   :: forall p s. KnownNat s
   => Integer -> Decimal p s
 fromUnderlyingValue n
-  = Decimal $ D.BigDecimal n (natVal (Proxy :: Proxy s))
+  = Decimal $ D.BigDecimal n (fromIntegral $ natVal (Proxy :: Proxy s))
 
 underlyingValue
   :: forall s p. (KnownNat p, KnownNat s)
@@ -25,9 +32,9 @@
 underlyingValue (Decimal d)
   = let ss = natVal (Proxy :: Proxy s)
         pp = natVal (Proxy :: Proxy p)
-        new = if ss > D.getScale d
-                 then D.BigDecimal (D.getValue d * 10 ^ (ss - D.getScale d)) ss
-                 else D.roundBD d (D.halfUp ss)
-    in if D.precision new > pp
+        new = if ss > intScale d
+                 then D.BigDecimal (D.value d * 10 ^ (ss - intScale d)) (fromIntegral ss)
+                 else D.roundBD d (D.halfUp (fromIntegral ss))
+    in if D.precision new > fromIntegral pp
           then Nothing
-          else Just $ fromInteger $ D.getValue new
+          else Just $ fromInteger $ D.value new
diff --git a/src/Data/Avro/Schema/Deconflict.hs b/src/Data/Avro/Schema/Deconflict.hs
--- a/src/Data/Avro/Schema/Deconflict.hs
+++ b/src/Data/Avro/Schema/Deconflict.hs
@@ -6,26 +6,17 @@
 import           Control.Applicative     ((<|>))
 import           Data.Avro.Schema.Schema as S
 import qualified Data.Foldable           as Foldable
-import           Data.HashMap.Strict     (HashMap)
-import qualified Data.HashMap.Strict     as HashMap
 import           Data.List               (find)
-import           Data.List.NonEmpty      (NonEmpty (..))
-import qualified Data.List.NonEmpty      as NE
-import qualified Data.Map                as M
 import           Data.Maybe              (isNothing)
-import           Data.Semigroup          ((<>))
 import qualified Data.Set                as Set
 import           Data.Text               (Text)
 import qualified Data.Text               as Text
-import qualified Data.Text.Encoding      as Text
 import           Data.Vector             (Vector)
 import qualified Data.Vector             as V
 
 import           Data.Avro.Schema.ReadSchema (FieldStatus (..), ReadField, ReadSchema)
 import qualified Data.Avro.Schema.ReadSchema as Read
 
-import Debug.Trace
-
 -- | @deconflict writer reader@ will produce a schema that can decode
 -- with the writer's schema into the form specified by the reader's schema.
 --
@@ -60,7 +51,7 @@
 deconflict (S.Map w) (S.Map r)       = Read.Map <$> deconflict w r
 
 deconflict w@S.Enum{} r@S.Enum{}
-  | name w == name r && symbols w `contains` symbols r = pure Read.Enum
+  | name w == name r && symbols r `contains` symbols w = pure Read.Enum
     { Read.name    = name r
     , Read.aliases = aliases w <> aliases r
     , Read.doc     = doc r
@@ -76,7 +67,7 @@
     }
 
 deconflict w@S.Record {} r@S.Record {}
-  | name w == name r = do
+  | name w == name r || name w `elem` aliases r = do
     fields' <- deconflictFields (fields w) (fields r)
     pure Read.Record
       { Read.name    = name r
@@ -138,5 +129,7 @@
 findTypeV :: Schema -> Vector Schema -> Maybe (Int, Schema)
 findTypeV schema schemas =
   let tn = typeName schema
-  in case V.findIndex ((tn ==) . typeName) schemas of
-      Just ix -> Just (ix, V.unsafeIndex schemas ix)
+      allNames typ =
+        typeName typ : map renderFullname (typeAliases typ)
+  in ((,) <$> id <*> V.unsafeIndex schemas) <$>
+        V.findIndex ((tn `elem`) . allNames) schemas
diff --git a/src/Data/Avro/Schema/ReadSchema.hs b/src/Data/Avro/Schema/ReadSchema.hs
--- a/src/Data/Avro/Schema/ReadSchema.hs
+++ b/src/Data/Avro/Schema/ReadSchema.hs
@@ -21,10 +21,8 @@
 import           Control.DeepSeq         (NFData)
 import           Data.Avro.Schema.Schema (LogicalTypeBytes, LogicalTypeFixed, LogicalTypeInt, LogicalTypeLong, LogicalTypeString, Order, TypeName)
 import qualified Data.Avro.Schema.Schema as S
-import           Data.HashMap.Strict     (HashMap)
 import qualified Data.HashMap.Strict     as HashMap
 import           Data.Text               (Text)
-import qualified Data.Text               as T
 import qualified Data.Vector             as V
 import           GHC.Generics            (Generic)
 
@@ -199,4 +197,5 @@
   f@Fixed{..}  -> HashMap.fromList $ (name : aliases) `zip` repeat f
   Array{..}    -> extractBindings item
   Map{..}      -> extractBindings values
+  FreeUnion {..} -> extractBindings ty
   _            -> HashMap.empty
diff --git a/src/Data/Avro/Schema/Schema.hs b/src/Data/Avro/Schema/Schema.hs
--- a/src/Data/Avro/Schema/Schema.hs
+++ b/src/Data/Avro/Schema/Schema.hs
@@ -37,6 +37,7 @@
   , validateSchema
   -- * Lower level utilities
   , typeName
+  , typeAliases
   , buildTypeEnvironment
   , extractBindings
 
@@ -63,29 +64,25 @@
 import qualified Control.Monad.Fail         as MF
 import           Control.Monad.State.Strict
 
-import           Data.Aeson             (FromJSON (..), ToJSON (..), object, (.!=), (.:), (.:!), (.:?), (.=))
+import           Data.Aeson             (FromJSON (..), ToJSON (..), object, (.!=), (.:!), (.:), (.:?), (.=))
 import qualified Data.Aeson             as A
+import qualified Data.Aeson.Key         as A
+import qualified Data.Aeson.KeyMap      as KM
 import           Data.Aeson.Types       (Parser, typeMismatch)
 import qualified Data.ByteString        as B
-import qualified Data.ByteString.Base16 as Base16
 import qualified Data.Char              as Char
 import           Data.Function          (on)
-import           Data.Hashable
 import           Data.HashMap.Strict    (HashMap)
 import qualified Data.HashMap.Strict    as HashMap
+import           Data.Hashable
 import           Data.Int
-import qualified Data.IntMap            as IM
-import qualified Data.List              as L
 import           Data.List.NonEmpty     (NonEmpty (..))
 import qualified Data.List.NonEmpty     as NE
 import           Data.Maybe             (catMaybes, fromMaybe, isJust)
-import           Data.Monoid            (First (..))
 import           Data.Semigroup
-import qualified Data.Set               as S
 import           Data.String
 import           Data.Text              (Text)
 import qualified Data.Text              as T
-import           Data.Text.Encoding     as T
 import qualified Data.Vector            as V
 import           Prelude                as P
 
@@ -147,9 +144,13 @@
               }
     deriving (Ord, Show, Generic, NFData)
 
+pattern Int' :: Schema
 pattern Int'    = Int    Nothing
+pattern Long' :: Schema
 pattern Long'   = Long   Nothing
+pattern Bytes' :: Schema
 pattern Bytes'  = Bytes  Nothing
+pattern String' :: Schema
 pattern String' = String Nothing
 
 data Field = Field { fldName    :: Text
@@ -164,30 +165,140 @@
 data Order = Ascending | Descending | Ignore
   deriving (Eq, Ord, Show, Generic, NFData)
 
-data Decimal
-  = Decimal { precision :: Integer, scale :: Integer }
-  deriving (Eq, Show, Ord, Generic, NFData)
+-- ** Logical Types
 
+-- $ In addition to primitive types, Avro supports [**logical
+-- types**](https://avro.apache.org/docs/current/spec.html#Logical+Types). A
+-- logical type is represented the same way as a primitive type, but
+-- has an extra annotation in the Avro schema that Avro libraries can
+-- use to generate more specific types.
+--
+-- Example:
+--
+-- @
+-- {
+--   "type": "int",
+--   "logicalType": "date"
+-- }
+-- @
+--
+-- The @date@ logical type represents a calendar date with no
+-- timezone. It is encoded as an Avro @int@ with the number of days
+-- from the Unix epoch (1970-01-01). Avro implementations /may/ parse
+-- this type into a language-specific date type (eg 'Data.Time.Day' in
+-- Haskell), but could also treat it as a normal Avro @int@ instead.
+
 newtype LogicalTypeBytes
   = DecimalB Decimal
+    -- ^ An arbitrary-precision signed decimal number. See 'Decimal'.
   deriving (Eq, Show, Ord, Generic, NFData)
 
 data LogicalTypeFixed
-  = DecimalF Decimal | Duration
+  = DecimalF Decimal
+    -- ^ An arbitrary-precision signed decimal number. See 'Decimal'.
+  | Duration
+    -- ^ An interval of time, represented as some number of months,
+    -- days and milliseconds.
+    --
+    -- Encoded as three little-endian unsigned integers for months,
+    -- days and milliseconds respectively.
   deriving (Eq, Show, Ord, Generic, NFData)
 
 data LogicalTypeInt
-  = DecimalI Decimal | Date | TimeMillis
+  = DecimalI Decimal
+    -- ^ An arbitrary-precision signed decimal number. See 'Decimal'.
+  | Date
+    -- ^ A date (eg @2020-01-10@) with no timezone/locale.
+    --
+    -- Encoded as the number of days before/after the Unix epoch
+    -- (1970-01-01).
+  | TimeMillis
+    -- ^ A time of day with millisecond precision.
+    --
+    -- Encoded as the number of milliseconds after midnight.
   deriving (Eq, Show, Ord, Generic, NFData)
 
 data LogicalTypeLong
-  = DecimalL Decimal | TimeMicros | TimestampMillis | TimestampMicros
+  = DecimalL Decimal
+    -- ^ An arbitrary-precision signed decimal number. See 'Decimal'.
+  | TimeMicros
+    -- ^ A time of day with microsecond precision.
+    --
+    -- Encoded as the number of microseconds after midnight.
+  | TimestampMillis
+    -- ^ A UTC timestamp with millisecond precision.
+    --
+    -- Encoded as the number of milliseconds before/after the Unix
+    -- epoch (1970-01-01 00:00:00.000).
+  | TimestampMicros
+    -- ^ A UTC timestamp with microsecond precision.
+    --
+    -- Encoded as the number of microseconds before/after the Unix
+    -- epoch (1970-01-01 00:00:00.000000).
+  | LocalTimestampMillis
+    -- ^ A timestamp in the local timezone, whatever that happens to
+    -- be, with millisecond precision.
+    --
+    -- Encoded as the number of milliseconds before/after the Unix
+    -- epoch (1970-01-01 00:00:00.000).
+  | LocalTimestampMicros
+    -- ^ A timestamp in the local timezone, whatever that happens to
+    -- be, with microsecond precision.
+    --
+    -- Encoded as the number of microseconds before/after the Unix
+    -- epoch (1970-01-01 00:00:00.000000).
   deriving (Eq, Show, Ord, Generic, NFData)
 
 data LogicalTypeString
   = UUID
+    -- ^ A Universally Unique Identifier (UUID).
+    --
+    -- Encoded as a string that is valid according to [RFC
+    -- 4122](https://www.ietf.org/rfc/rfc4122.txt).
   deriving (Eq, Show, Ord, Generic, NFData)
 
+-- | The @decimal@ logical type represents arbitrary-precision decimal
+-- numbers. Numbers are represented as @unscaled * (10 ** -scale)@
+-- where @scale@ is part of the logical type and @unscaled@ is an
+-- integer represented by the underlying primitive type.
+--
+-- Instances of the @decimal@ logical type need to specify a @scale@
+-- and @precision@.
+--
+-- @decimal@ can be encoded as one of several different primitive
+-- types:
+--
+--  * @bytes@
+--  * @fixed@
+--  * @long@
+--  * @int@
+--
+-- For @long@ and @int@, @unscaled@ is the underlying number.
+--
+-- For @bytes@ and @fixed@, @unscaled@ is represented as a
+-- two's-complement signed integer in big-endian byte order.
+--
+-- Note: @int@ and @long@ representations for @decimal@ are not part
+-- of the [current Avro
+-- specification](https://avro.apache.org/docs/current/spec.html#Decimal),
+-- but they are supported by some language implementations including
+-- the official Java library. Implementations that do not support this
+-- should ignore the logical type and use the underlying primitive
+-- type instead.
+data Decimal
+  = Decimal { precision :: Integer
+              -- ^ The maximum number of digits that can be
+              -- represented by this @decimal@ type.
+              --
+              -- @precision > 0@
+            , scale     :: Integer
+              -- ^ The @scale@ in @unscaled * (10 ** -scale)@ for this
+              -- type.
+              --
+              -- @0 ≤ scale ≤ precision@
+            }
+  deriving (Eq, Show, Ord, Generic, NFData)
+
 instance Eq Schema where
   Null == Null = True
   Boolean == Boolean = True
@@ -320,9 +431,9 @@
            -> TypeName
               -- ^ The resulting /fullname/ of the generated type,
               -- according to the rules laid out above.
-mkTypeName context name ns
+mkTypeName context name namespace_
   | isFullName name = parseFullname name
-  | otherwise       = case ns of
+  | otherwise       = case namespace_ of
       Just ns -> TN name $ filter (/= "") (T.splitOn "." ns)
       Nothing -> TN name $ maybe [] namespace context
   where isFullName = isJust . T.find (== '.')
@@ -360,6 +471,10 @@
                     -> "timestamp-millis"
     Long (Just TimestampMicros)
                     -> "timestamp-micros"
+    Long (Just LocalTimestampMillis)
+                    -> "local-timestamp-millis"
+    Long (Just LocalTimestampMicros)
+                    -> "local-timestamp-micros"
     Float           -> "float"
     Double          -> "double"
     Bytes Nothing   -> "bytes"
@@ -380,6 +495,15 @@
   where
     decimalName (Decimal prec sc) = "decimal(" <> T.pack (show prec) <> "," <> T.pack (show sc) <> ")"
 
+-- |Get the aliases of the type.
+typeAliases :: Schema -> [TypeName]
+typeAliases bt =
+  case bt of
+    Record { aliases } -> aliases
+    Enum { aliases} -> aliases
+    Fixed { aliases } -> aliases
+    _ -> []
+
 instance FromJSON Schema where
   parseJSON = parseSchemaJSON Nothing
 
@@ -395,21 +519,23 @@
                 -> Parser Schema
 parseSchemaJSON context = \case
   A.String s -> case s of
-    "null"             -> return Null
-    "boolean"          -> return Boolean
-    "int"              -> return $ Int Nothing
-    "long"             -> return $ Long Nothing
-    "float"            -> return Float
-    "double"           -> return Double
-    "bytes"            -> return $ Bytes Nothing
-    "string"           -> return $ String Nothing
-    "uuid"             -> return $ String (Just UUID)
-    "date"             -> return $ Int (Just Date)
-    "time-millis"      -> return $ Int (Just TimeMillis)
-    "time-micros"      -> return $ Long (Just TimeMicros)
-    "timestamp-millis" -> return $ Long (Just TimestampMillis)
-    "timestamp-micros" -> return $ Long (Just TimestampMicros)
-    somename           -> return $ NamedType $ mkTypeName context somename Nothing
+    "null"                   -> return Null
+    "boolean"                -> return Boolean
+    "int"                    -> return $ Int Nothing
+    "long"                   -> return $ Long Nothing
+    "float"                  -> return Float
+    "double"                 -> return Double
+    "bytes"                  -> return $ Bytes Nothing
+    "string"                 -> return $ String Nothing
+    "uuid"                   -> return $ String (Just UUID)
+    "date"                   -> return $ Int (Just Date)
+    "time-millis"            -> return $ Int (Just TimeMillis)
+    "time-micros"            -> return $ Long (Just TimeMicros)
+    "timestamp-millis"       -> return $ Long (Just TimestampMillis)
+    "timestamp-micros"       -> return $ Long (Just TimestampMicros)
+    "local-timestamp-millis" -> return $ Long (Just LocalTimestampMillis)
+    "local-timestamp-micros" -> return $ Long (Just LocalTimestampMicros)
+    somename                 -> return $ NamedType $ mkTypeName context somename Nothing
   A.Array arr
     | V.length arr > 0 ->
       Union <$> V.mapM (parseSchemaJSON context) arr
@@ -447,6 +573,12 @@
       Just "timestamp-micros" -> case ty of
           "long" -> pure $ Long (Just TimestampMicros)
           s      -> fail $ "Unsupported underlying type: " <> T.unpack s
+      Just "local-timestamp-millis" -> case ty of
+          "long" -> pure $ Long (Just LocalTimestampMillis)
+          s      -> fail $ "Unsupported underlying type: " <> T.unpack s
+      Just "local-timestamp-micros" -> case ty of
+          "long" -> pure $ Long (Just LocalTimestampMicros)
+          s      -> fail $ "Unsupported underlying type: " <> T.unpack s
       Just "duration" -> case ty of
           "fixed" -> (\fx -> fx { logicalTypeF = Just Duration }) <$> parseFixed o
           s       -> fail $ "Unsupported underlying type: " <> T.unpack s
@@ -455,23 +587,21 @@
         "map"    -> Map <$> (parseSchemaJSON context =<< o .: "values")
         "array"  -> Array <$> (parseSchemaJSON context =<< o .: "items")
         "record" -> do
-          name      <- o .: "name"
-          namespace <- o .:? "namespace"
-          let typeName = mkTypeName context name namespace
-              mkAlias name = mkTypeName (Just typeName) name Nothing
-          aliases <- mkAliases typeName <$> (o .:? "aliases" .!= [])
-          doc     <- o .:? "doc"
-          fields  <- mapM (parseField typeName) =<< (o .: "fields")
-          pure $ Record typeName aliases doc fields
+          name       <- o .: "name"
+          namespace  <- o .:? "namespace"
+          let recName = mkTypeName context name namespace
+          aliases    <- mkAliases recName <$> (o .:? "aliases" .!= [])
+          doc        <- o .:? "doc"
+          fields     <- mapM (parseField recName) =<< (o .: "fields")
+          pure $ Record recName aliases doc fields
         "enum"   -> do
-          name      <- o .: "name"
-          namespace <- o .:? "namespace"
-          let typeName = mkTypeName context name namespace
-              mkAlias name = mkTypeName (Just typeName) name Nothing
-          aliases <- mkAliases typeName <$> (o .:? "aliases" .!= [])
-          doc     <- o .:? "doc"
-          symbols <- o .: "symbols"
-          pure $ mkEnum typeName aliases doc symbols
+          name        <- o .: "name"
+          namespace   <- o .:? "namespace"
+          let enumName = mkTypeName context name namespace
+          aliases     <- mkAliases enumName <$> (o .:? "aliases" .!= [])
+          doc         <- o .:? "doc"
+          symbols     <- o .: "symbols"
+          pure $ mkEnum enumName aliases doc symbols
         "fixed"   -> parseFixed o
         "null"    -> pure Null
         "boolean" -> pure Boolean
@@ -487,13 +617,12 @@
 
   where
     parseFixed o = do
-      name      <- o .: "name"
-      namespace <- o .:? "namespace"
-      let typeName = mkTypeName context name namespace
-          mkAlias name = mkTypeName (Just typeName) name Nothing
-      aliases <- mkAliases typeName <$> (o .:? "aliases" .!= [])
-      size    <- o .: "size"
-      pure $ Fixed typeName aliases size Nothing
+      name         <- o .: "name"
+      namespace    <- o .:? "namespace"
+      let fixedName = mkTypeName context name namespace
+      aliases      <- mkAliases fixedName <$> (o .:? "aliases" .!= [])
+      size         <- o .: "size"
+      pure $ Fixed fixedName aliases size Nothing
 
 -- | Parse aliases, inferring the namespace based on the type being aliases.
 mkAliases :: TypeName
@@ -523,9 +652,7 @@
       Just (Success x) -> return (Just x)
       Just (Error e)   -> fail e
       Nothing          -> return Nothing
-    order <- o .:? ("order" :: Text)    .!= Just Ascending
-
-    let mkAlias name = mkTypeName (Just record) name Nothing
+    order <- o .:? "order" .!= Just Ascending
     aliases  <- o .:? "aliases"  .!= []
     return $ Field name aliases doc order ty def
   invalid    -> typeMismatch "Field" invalid
@@ -566,6 +693,10 @@
     object [ "type" .= ("long" :: Text), "logicalType" .= ("timestamp-millis" :: Text) ]
   Long (Just TimestampMicros) ->
     object [ "type" .= ("long" :: Text), "logicalType" .= ("timestamp-micros" :: Text) ]
+  Long (Just LocalTimestampMillis) ->
+    object [ "type" .= ("long" :: Text), "logicalType" .= ("local-timestamp-millis" :: Text) ]
+  Long (Just LocalTimestampMicros) ->
+    object [ "type" .= ("long" :: Text), "logicalType" .= ("local-timestamp-micros" :: Text) ]
   Float           -> A.String "float"
   Double          -> A.String "double"
   Bytes Nothing   -> A.String "bytes"
@@ -613,12 +744,12 @@
                    -> [ "logicalType" .= ("decimal" :: Text)
                       , "precision" .= prec, "scale" .= sc ]
     in object (basic ++ extended)
-  where render context typeName
-          | Just ctx <- context
-          , namespace ctx == namespace typeName = baseName typeName
-          | otherwise                           = renderFullname typeName
+  where render context1 typeName1
+          | Just ctx <- context1
+          , namespace ctx == namespace typeName1 = baseName typeName1
+          | otherwise                            = renderFullname typeName1
 
-        fieldToJSON context Field {..} =
+        fieldToJSON context1 Field {..} =
           let opts = catMaybes
                 [ ("order" .=)     <$> fldOrder
                 , ("doc" .=)       <$> fldDoc
@@ -626,7 +757,7 @@
                 ]
           in object $ opts ++
              [ "name"    .= fldName
-             , "type"    .= schemaToJSON (Just context) fldType
+             , "type"    .= schemaToJSON (Just context1) fldType
              , "aliases" .= fldAliases
              ]
 
@@ -647,10 +778,10 @@
       DBytes _ bs      -> A.String (serializeBytes bs)
       DString _ t      -> A.String t
       DArray vec       -> A.Array (V.map toJSON vec)
-      DMap mp          -> A.Object (HashMap.map toJSON mp)
-      DRecord _ flds   -> A.Object (HashMap.map toJSON flds)
+      DMap mp          -> A.Object $ fmap toJSON (KM.fromHashMapText mp)
+      DRecord _ flds   -> A.Object $ fmap toJSON (KM.fromHashMapText flds)
       DUnion _ _ DNull -> A.Null
-      DUnion _ ty val  -> object [ typeName ty .= val ]
+      DUnion _ ty val  -> object [ A.fromText (typeName ty) .= val ]
       DFixed _ bs      -> A.String (serializeBytes bs)
       DEnum _ _ txt    -> A.String txt
 
@@ -670,7 +801,7 @@
 instance Monad Result where
   return = pure
   Success a >>= k = k a
-  Error e >>= _ = Error e
+  Error e >>= _   = Error e
 #if !MIN_VERSION_base(4,13,0)
   fail = MF.fail
 #endif
@@ -774,10 +905,10 @@
           _       -> avroTypeMismatch ty "array"
       A.Object obj ->
         case ty of
-          Map mTy     -> DMap <$> mapM (parseAvroJSON union env mTy) obj
+          Map mTy     -> DMap <$> mapM (parseAvroJSON union env mTy) (KM.toHashMapText obj)
           Record {..} ->
            do let lkAndParse f =
-                    case HashMap.lookup (fldName f) obj of
+                    case KM.lookup (A.fromText (fldName f)) obj of
                       Nothing -> case fldDefault f of
                                   Just v  -> return v
                                   Nothing -> fail $ "Decode failure: No record field '" <> T.unpack (fldName f) <> "' and no default in schema."
@@ -903,7 +1034,7 @@
       t@(NamedType n)   -> fromMaybe t <$> gets (HashMap.lookup n)
       a@Array{item}     -> (\x -> a { item = x })   <$> go item
       m@Map{values}     -> (\x -> m { values = x }) <$> go values
-      u@Union{options}  -> Union <$> traverse go options
+      Union{options}    -> Union <$> traverse go options
 
       r@Record{name, fields}  -> do
         fields' <- traverse expandField fields
@@ -926,11 +1057,11 @@
     overlayType  a@Array{..}      = a { item    = overlayType item }
     overlayType  m@Map{..}        = m { values  = overlayType values }
     overlayType  r@Record{..}     = r { fields  = map overlayField fields }
-    overlayType  u@Union{..}      = Union (fmap overlayType options)
-    overlayType  nt@(NamedType _) = rebind nt
+    overlayType  Union{..}        = Union (fmap overlayType options)
+    overlayType  (NamedType nt)   = rebind nt
     overlayType  other            = other
 
-    rebind (NamedType tn) = HashMap.lookupDefault (NamedType tn) tn bindings
+    rebind tn             = HashMap.lookupDefault (NamedType tn) tn bindings
     bindings              = extractBindings supplement
 
 -- | Extract the named inner type definition as its own schema.
diff --git a/test/Avro/Data/Logical.hs b/test/Avro/Data/Logical.hs
--- a/test/Avro/Data/Logical.hs
+++ b/test/Avro/Data/Logical.hs
@@ -15,7 +15,7 @@
 module Avro.Data.Logical
 where
 
-import Data.Avro.Internal.Time (microsToDiffTime, microsToUTCTime, millisToDiffTime, millisToUTCTime)
+import Data.Avro.Internal.Time (microsToDiffTime, microsToLocalTime, microsToUTCTime, millisToDiffTime, millisToLocalTime, millisToUTCTime)
 
 import Data.Avro.Deriving (deriveAvroFromByteString, r)
 
@@ -59,7 +59,23 @@
           "logicalType": "time-micros",
           "type": "long"
         }
-    }
+    },
+    {
+      "name": "localTimestampMillis",
+      "type":
+        {
+          "logicalType": "local-timestamp-millis",
+          "type": "long"
+        }
+     },
+     {
+      "name": "localTimestampMicros",
+      "type":
+        {
+          "logicalType": "local-timestamp-micros",
+          "type": "long"
+        }
+     }
   ]
 }
 |]
@@ -70,3 +86,5 @@
   <*> (microsToUTCTime  . toInteger <$> Gen.int64 (Range.linear 0 maxBound))
   <*> (millisToDiffTime . toInteger <$> Gen.int32 (Range.linear 0 maxBound))
   <*> (microsToDiffTime . toInteger <$> Gen.int64 (Range.linear 0 maxBound))
+  <*> (millisToLocalTime . toInteger <$> Gen.int64 (Range.linear 0 maxBound))
+  <*> (microsToLocalTime . toInteger <$> Gen.int64 (Range.linear 0 maxBound))
diff --git a/test/Avro/Data/TwoBits.hs b/test/Avro/Data/TwoBits.hs
new file mode 100644
--- /dev/null
+++ b/test/Avro/Data/TwoBits.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE DeriveAnyClass      #-}
+{-# LANGUAGE DeriveGeneric       #-}
+{-# LANGUAGE DeriveTraversable   #-}
+{-# LANGUAGE NumDecimals         #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE QuasiQuotes         #-}
+{-# LANGUAGE RankNTypes          #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving  #-}
+{-# LANGUAGE StrictData          #-}
+{-# LANGUAGE TemplateHaskell     #-}
+{-# LANGUAGE TupleSections       #-}
+{-# LANGUAGE TypeApplications    #-}
+
+module Avro.Data.TwoBits
+where
+
+import Data.Avro.Deriving (deriveAvroFromByteString, r)
+
+import Hedgehog
+import Hedgehog.Gen   as Gen
+import Hedgehog.Range as Range
+
+import qualified Data.ByteString.Lazy as LBS
+
+twoBits'rawSchema :: LBS.ByteString
+twoBits'rawSchema = [r|
+{
+  "type": "record",
+  "name": "TwoBits",
+  "fields": [
+    { "name": "bit0",
+      "type": {
+        "type": "enum",
+        "name": "Bit",
+        "symbols": [ "Zero", "One"]
+      }
+    },
+    { "name": "bit1",
+      "type": "Bit"
+    }
+  ]
+}
+|]
+
+
+deriveAvroFromByteString [r|
+{
+  "type": "record",
+  "name": "TwoBits",
+  "fields": [
+    { "name": "bit0",
+      "type": {
+        "type": "enum",
+        "name": "Bit",
+        "symbols": [ "Zero", "One"]
+      }
+    },
+    { "name": "bit1",
+      "type": "Bit"
+    }
+  ]
+}
+|]
diff --git a/test/Avro/DefaultsSpec.hs b/test/Avro/DefaultsSpec.hs
--- a/test/Avro/DefaultsSpec.hs
+++ b/test/Avro/DefaultsSpec.hs
@@ -4,6 +4,7 @@
 where
 
 import qualified Data.Aeson              as J
+import qualified Data.Aeson.KeyMap       as KM
 import           Data.Avro.Schema.Schema
 import qualified Data.HashMap.Strict     as M
 import           Data.List.NonEmpty      (NonEmpty (..))
@@ -36,6 +37,6 @@
     let
       msgSchema = schema'MaybeTest
       (J.Object jSchema) = J.toJSON msgSchema
-      (Just (J.Array flds)) = M.lookup "fields" jSchema
+      (Just (J.Array flds)) = KM.lookup "fields" jSchema
       (J.Object jFld) = V.head flds
-    in M.lookup "default" jFld `shouldBe` Just J.Null
+    in KM.lookup "default" jFld `shouldBe` Just J.Null
diff --git a/test/Avro/Gen/Schema.hs b/test/Avro/Gen/Schema.hs
--- a/test/Avro/Gen/Schema.hs
+++ b/test/Avro/Gen/Schema.hs
@@ -27,4 +27,6 @@
 long :: MonadGen m => m Schema
 long = do
   dec <- decimalGen
-  Long <$> Gen.maybe (Gen.element [DecimalL dec, TimeMicros, TimestampMillis, TimestampMicros])
+  Long <$> Gen.maybe (Gen.element
+    [DecimalL dec, TimeMicros, TimestampMillis,
+     TimestampMicros, LocalTimestampMillis, LocalTimestampMicros])
diff --git a/test/Avro/NormSchemaSpec.hs b/test/Avro/NormSchemaSpec.hs
--- a/test/Avro/NormSchemaSpec.hs
+++ b/test/Avro/NormSchemaSpec.hs
@@ -1,14 +1,16 @@
 {-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
 module Avro.NormSchemaSpec
 where
 
 import           Data.Avro.Schema.Schema (Schema (..), fields, fldType, mkUnion)
 import           Data.List.NonEmpty      (NonEmpty (..))
 import qualified Data.Set                as S
-
-import Avro.Data.Karma
-import Avro.Data.Reused
+import qualified Data.Aeson              as Aeson
+import           Avro.Data.Karma
+import           Avro.Data.Reused
+import           Avro.Data.TwoBits
 
 import Test.Hspec
 
@@ -21,3 +23,7 @@
 
   it "should normalise schemas from unions" $
      fldType <$> fields schema'Curse `shouldBe` [mkUnion (Null :| [schema'Geo])]
+
+  it "should serialise reused schema correctly" $
+    let Just expected = Aeson.encode <$> Aeson.decode @Schema twoBits'rawSchema
+    in Aeson.encode schema'TwoBits `shouldBe` expected
