diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Revision history for json-query
 
+## 0.2.2.0 -- 2023-08-09
+
+* Add `Json.Errors.hPut` to make it less verbose to print
+  parse errors.
+* Add `Json.Parser.foldSmallArray`
+* Add variant of `Json.Path.query` that returns Null when path is
+  not found.
+
 ## 0.2.1.0 -- 2021-07-15
 
 * Allow building with GHC 9.2.3
diff --git a/json-query.cabal b/json-query.cabal
--- a/json-query.cabal
+++ b/json-query.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name: json-query
-version: 0.2.1.0
+version: 0.2.2.0
 synopsis: Kitchen sink for querying JSON
 description:
   The library complements json-syntax by making available several
@@ -31,8 +31,8 @@
     , bytestring >=0.10 && <0.12
     , contiguous >=0.6.1
     , json-syntax >=0.2.2 && <0.3
-    , primitive >=0.7 && <0.8
-    , primitive-unlifted >=0.1.3 && <0.2
+    , primitive >=0.7 && <0.10
+    , primitive-unlifted >=0.1.3 && <2.2
     , profunctors >=5.6
     , scientific-notation >=0.1.5 && <0.2
     , text-short >=0.1.3 && <0.2
diff --git a/src/Json/Errors.hs b/src/Json/Errors.hs
--- a/src/Json/Errors.hs
+++ b/src/Json/Errors.hs
@@ -10,18 +10,20 @@
     -- * Encoding
   , encode
   , builderUtf8
+  , hPut
     -- * Create
   , singleton
     -- * Conversion
   , toSmallArray
   ) where
 
+import Control.Monad.ST (runST)
+import Data.ByteString.Short.Internal (ShortByteString(SBS))
 import Data.Bytes.Builder (Builder)
 import Data.Primitive (SmallArray)
 import Data.Text.Short (ShortText)
-import Data.ByteString.Short.Internal (ShortByteString(SBS))
-import Control.Monad.ST (runST)
 import Json.Error (Error)
+import System.IO (Handle)
 
 import qualified Data.Bytes.Builder as Builder
 import qualified Data.Bytes.Chunks as ByteChunks
@@ -48,6 +50,8 @@
 singleton :: Error -> Errors
 singleton = ErrorsOne
 
+-- | Convert errors to builder. The errors are separated by
+-- a pair of characters: comma and space.
 builderUtf8 :: Errors -> Builder
 builderUtf8 errs =
   let len = countErrors errs
@@ -57,6 +61,20 @@
       Arr.foldMap
         (\e -> Builder.ascii2 ',' ' ' <> Error.builderUtf8 e)
         (Arr.slice errArr 1 (len - 1))
+
+-- | Print errors to the provided handle. Typically, @System.IO.stderr@
+-- is provided as the handle. Each encoded error is suffixed with a newline.
+--
+-- This is a convenience function for the common case where, after a
+-- failed parse, an application prints out all parse errors and then exits.
+hPut :: Handle -> Errors -> IO ()
+hPut h errs = do
+  let len = countErrors errs
+      errArr = makeErrorArray len errs
+      bldr = Arr.foldMap
+        (\e -> Error.builderUtf8 e <> Builder.ascii '\n')
+        errArr
+   in ByteChunks.hPut h (Builder.run 128 bldr)
 
 -- | Convert errors to array.
 toSmallArray :: Errors -> SmallArray Error
diff --git a/src/Json/Parser.hs b/src/Json/Parser.hs
--- a/src/Json/Parser.hs
+++ b/src/Json/Parser.hs
@@ -20,6 +20,7 @@
   , members
     -- * Arrays
   , smallArray
+  , foldSmallArray
   , traverseMembers
     -- * Specific Data Constructors
   , object
@@ -179,7 +180,7 @@
     Nothing -> Left (Errors.singleton (Error{context=p',message="key not found: " <> name}))
     Just Member{value} -> runParser (f value) p'
 
--- | Variant of 'key' that supplies the JSON value @null to the
+-- | Variant of 'key' that supplies the JSON value @null@ to the
 -- callback if the key is not found. Using this parser combinators implies
 -- that there is no distinction between @null@ and an absent value in
 -- the encoding scheme.
@@ -215,6 +216,21 @@
         pure (ix + 1)
       ) 0 xs
     lift (PM.unsafeFreezeSmallArray dst)
+
+-- | Run the parser against every element in a 'SmallArray', updating an
+-- accumulator at each step. Folds from left to right. This adjusts
+-- the context at each element. Typically, type @a@ is @Value@.
+foldSmallArray :: (b -> a -> Parser b) -> b -> SmallArray a -> Parser b
+{-# inline foldSmallArray #-}
+foldSmallArray f acc0 xs = Parser $ \ !p -> runST do
+  -- TODO: make sure tuples get elided
+  runExceptT $ do
+    (!_,r) <- foldlM
+      (\( !ix, !acc) x -> do
+        !acc' <- ExceptT (pure (runParser (f acc x) (Index ix p)))
+        pure (ix + 1, acc')
+      ) (0,acc0) xs
+    pure r
 
 -- | Traverse the members. The adjusts the context at each member.
 traverseMembers :: (Member -> Parser a) -> SmallArray Member -> Parser (SmallArray a)
diff --git a/src/Json/Path.hs b/src/Json/Path.hs
--- a/src/Json/Path.hs
+++ b/src/Json/Path.hs
@@ -9,13 +9,14 @@
   , builderUtf8
     -- * Lookup
   , query
+  , query'
     -- * Reverse
   , reverse
   ) where
 
 import Prelude hiding (reverse)
 
-import Json (Value(Object,Array),Member(Member))
+import Json (Value(Object,Array,Null),Member(Member))
 import Data.Primitive (ByteArray(ByteArray))
 import Data.Text.Short (ShortText)
 import Data.Bytes.Builder (Builder)
@@ -53,7 +54,8 @@
     <> Builder.ascii ']'
     <> go p
 
--- | Search for an element at the given path.
+-- | Search for an element at the given path. Returns 'Nothing' if
+-- anything in the path is missing.
 query :: Path -> Value -> Maybe Value
 query = go where
   go Nil v = Just v
@@ -68,6 +70,23 @@
        in go p e
     else Nothing
   go _ _ = Nothing
+
+-- | Variant of 'query' that returns 'Null' if anything in the path
+-- is missing.
+query' :: Path -> Value -> Value
+query' = go where
+  go Nil v = v
+  go (Key k p) (Object mbrs) = go p $ foldr
+    (\(Member key val) other -> if key == k
+      then val
+      else other
+    ) Null mbrs
+  go (Index i p) (Array vs) = if i < PM.sizeofSmallArray vs
+    then
+      let !(# e #) = PM.indexSmallArray## vs i
+       in go p e
+    else Null
+  go _ _ = Null
 
 ba2st :: ByteArray -> ShortText
 ba2st (ByteArray x) = TS.fromShortByteStringUnsafe (SBS x)
