diff --git a/hw-aeson.cabal b/hw-aeson.cabal
--- a/hw-aeson.cabal
+++ b/hw-aeson.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.2
 
 name:                   hw-aeson
-version:                0.1.1.1
+version:                0.1.2.0
 synopsis:               Convenience functions for Aeson
 description:            Convenience functions for Aeson.
 category:               Data, JSON
diff --git a/src/HaskellWorks/Data/Aeson.hs b/src/HaskellWorks/Data/Aeson.hs
--- a/src/HaskellWorks/Data/Aeson.hs
+++ b/src/HaskellWorks/Data/Aeson.hs
@@ -2,6 +2,8 @@
 
 module HaskellWorks.Data.Aeson
     ( JsonEndo(..)
+    , WithJsonKeyValues(..)
+    , ToJsonKeyValues(..)
     , objectWithoutNulls
     , readJson
     , objectEndo
@@ -60,3 +62,37 @@
 -- @
 objectEndo :: [JsonEndo Pair] -> Value
 objectEndo es = object $ unJsonEndo (mconcat es) []
+
+-- | Generate key values from a value of a type.  This can be used
+-- in conjunction with 'WithJsonKeyValues' to define a 'ToJSON' instance
+-- without having to implement both 'toJSON' and 'toEncoding', thereby
+-- reducing boilerplate.
+--
+-- For example:
+--
+-- @
+-- instance ToJsonEncoding MyType where
+--   toJsonEncoding sv =
+--     [ "my_field" .!= sv ^. #myField
+--     ]
+-- @
+class ToJsonKeyValues a where
+  toJsonKeyValues :: (KeyValue kv, Monoid kv) => a -> [kv]
+
+-- | For use with language extension DerivingVia.  This derivation provides
+-- a ToJSON instance that delegates to the ToJsonKeyValues instance.
+--
+-- For example:
+--
+-- @
+-- newtype MyType = MyType
+--   { myField :: Text
+--   } deriving J.ToJSON via WithJsonKeyValues MyType
+-- @
+newtype WithJsonKeyValues a = WithJsonKeyValues
+  { unWithJsonKeyValues :: a
+  }
+
+instance ToJsonKeyValues a => ToJSON (WithJsonKeyValues a) where
+  toJSON = objectEndo . toJsonKeyValues . unWithJsonKeyValues
+  toEncoding = pairs . mconcat . toJsonKeyValues . unWithJsonKeyValues
