diff --git a/buffer-builder.cabal b/buffer-builder.cabal
--- a/buffer-builder.cabal
+++ b/buffer-builder.cabal
@@ -1,5 +1,5 @@
 name:                buffer-builder
-version:             0.2.1.0
+version:             0.2.2.0
 synopsis:            Library for efficiently building up buffers, one piece at a time
 description:
 
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,9 @@
-0.2.0.4
+0.2.2.0
 
+* Add the ability to encode custom types as JSON keys
+
+0.2.1.0
+
 * Fix a buffer overrun in the double serializer
+* Add support for URL percent-encoding
+* Tweak the BufferWriter struct to improve code generation
diff --git a/src/Data/BufferBuilder/Json.hs b/src/Data/BufferBuilder/Json.hs
--- a/src/Data/BufferBuilder/Json.hs
+++ b/src/Data/BufferBuilder/Json.hs
@@ -23,11 +23,16 @@
     , ToJson (..)
     , encodeJson
 
+    -- * Encoding Strings
+    , JsonString
+    , ToJsonString (..)
+
     -- * Objects
     , ObjectBuilder
     , emptyObject
     , (.=)
     , (.=#)
+    , row
 
     -- * Arrays
     , array
@@ -36,8 +41,12 @@
     , nullValue
 
     -- * Unsafe
-    , unsafeAppendBS
+    , unsafeValueUtf8Builder
+    , unsafeStringUtf8Builder
+
+    -- * Deprecated
     , unsafeAppendUtf8Builder
+    , unsafeAppendBS
     ) where
 
 import GHC.Base
@@ -62,7 +71,7 @@
 -- from primitives like 'emptyObject', 'array', and 'null'.
 --
 -- In special cases, or when performance is of utmost importance, the
--- unsafe functions 'unsafeAppendBS' and 'unsafeAppendUtf8Builder' are
+-- unsafe functions 'unsafeAppendUtf8Builder' are
 -- available.
 --
 -- Internally, Value encodes an action or sequence of actions that append
@@ -96,6 +105,17 @@
 encodeJson = UB.runUtf8Builder . utf8Builder . toJson
 {-# INLINE encodeJson #-}
 
+---- String
+
+-- | Represents a JSON string.
+newtype JsonString = JsonString { unJsonString :: Utf8Builder () }
+
+-- | The class of types that can be converted to JSON strings.  Any
+-- type that provides ToJsonString also provides ToJson, and thus can
+-- be used as JSON values.
+class ToJson a => ToJsonString a where
+    toJsonString :: a -> JsonString
+
 ---- Objects
 
 -- | Builds a JSON object.
@@ -150,6 +170,16 @@
 emptyObject = toJson NoPair
 {-# INLINE emptyObject #-}
 
+-- | Create an ObjectBuilder from an arbitrary key and value.  The key can be any
+-- type with a 'ToJsonString' instance.
+row :: (ToJsonString k, ToJson v) => k -> v -> ObjectBuilder
+row k v = Pair $ do
+    unJsonString $ toJsonString k
+    UB.appendChar7 ':'
+    utf8Builder $ toJson v
+infixr 8 `row`
+{-# INLINE row #-}
+
 -- | Create an 'ObjectBuilder' from a key and a value.
 (.=) :: ToJson a => Text -> a -> ObjectBuilder
 a .= b = Pair $ do
@@ -298,7 +328,7 @@
 
 instance ToJson Text where
     {-# INLINE toJson #-}
-    toJson txt = Value $ UB.appendEscapedJsonText txt
+    toJson text = Value $ UB.appendEscapedJsonText text
 
 instance ToJson Double where
     {-# INLINE toJson #-}
@@ -308,17 +338,38 @@
     {-# INLINE toJson #-}
     toJson a = Value $ UB.appendDecimalSignedInt a
 
+instance ToJsonString JsonString where
+    toJsonString = id
 
+instance ToJson JsonString where
+    toJson = Value . unJsonString
+
+instance ToJsonString Text where
+    {-# INLINE toJsonString #-}
+    toJsonString text = JsonString $ UB.appendEscapedJsonText text
+
+
 ---- Unsafe functions
 
--- | Unsafely append a string into a JSON document.
--- This function does /not/ escape, quote, or otherwise decorate the string in any way.
+-- | Unsafely convert a 'Utf8Builder' into a JSON value.
+-- This function does not escape, quote, or decorate the string in any way.
 -- This function is /unsafe/ because you can trivially use it to generate illegal JSON.
-unsafeAppendBS :: ByteString -> Value
-unsafeAppendBS bs = Value $ UB.unsafeAppendBS bs
+unsafeValueUtf8Builder :: Utf8Builder () -> Value
+unsafeValueUtf8Builder = Value
 
--- | Unsafely append a 'Utf8Builder' into a JSON document.
+-- | Unsafely convert a 'Utf8Builder' into a JSON string.
 -- This function does not escape, quote, or decorate the string in any way.
 -- This function is /unsafe/ because you can trivially use it to generate illegal JSON.
+unsafeStringUtf8Builder :: Utf8Builder () -> JsonString
+unsafeStringUtf8Builder = JsonString
+
+
+---- Deprecated
+
+{-# DEPRECATED unsafeAppendBS "Use unsafeValueUtf8Builder or unsafeStringUtf8Builder instead" #-}
+unsafeAppendBS :: ByteString -> Value
+unsafeAppendBS bs = Value $ UB.unsafeAppendBS bs
+
+{-# DEPRECATED unsafeAppendUtf8Builder "Use unsafeValueUtf8Builder or unsafeStringUtf8Builder instead" #-}
 unsafeAppendUtf8Builder :: Utf8Builder () -> Value
-unsafeAppendUtf8Builder utf8b = Value utf8b
+unsafeAppendUtf8Builder = unsafeValueUtf8Builder
diff --git a/test/JsonTest.hs b/test/JsonTest.hs
--- a/test/JsonTest.hs
+++ b/test/JsonTest.hs
@@ -11,6 +11,7 @@
 import Data.Monoid ((<>), Monoid (mconcat, mempty))
 import Data.String (IsString (..))
 import Data.BufferBuilder.Json
+import qualified Data.BufferBuilder.Utf8 as BBU
 import qualified Data.Aeson as Aeson
 import qualified Data.Aeson.Parser as JsonParse
 import qualified Data.ByteString as BS
@@ -45,6 +46,28 @@
         (encodeJson ("key" .= ("value" :: Text) <> "key2" .= ([5,6,7] :: [Int])))
     ae "{}" $ encodeJson $ ((mempty :: ObjectBuilder) <> mempty) <> (mempty <> mempty)
     ae "[]" (encodeJson ([] :: [Int]))
+
+data CustomThing = CustomThing
+
+encodeCustomThing :: CustomThing -> BBU.Utf8Builder ()
+encodeCustomThing _ = do
+    BBU.appendChar7 '"'
+    BBU.appendBS7 "foo"
+    BBU.appendChar7 '/'
+    BBU.appendBS7 "bar"
+    BBU.appendChar7 '/'
+    BBU.appendBS7 "baz"
+    BBU.appendChar7 '"'
+
+instance ToJson CustomThing where
+    toJson = unsafeValueUtf8Builder . encodeCustomThing
+instance ToJsonString CustomThing where
+    toJsonString = unsafeStringUtf8Builder . encodeCustomThing
+
+case_encode_object_with_custom_typeclasses :: IO ()
+case_encode_object_with_custom_typeclasses = do
+    ae "{\"key\":\"value\"}" $ encodeJson $ ("key" :: Text) `row` ("value" :: Text)
+    ae "{\"foo/bar/baz\":\"foo/bar/baz\"}" $ encodeJson $ CustomThing `row` CustomThing
 
 case_monoid_laws :: IO ()
 case_monoid_laws = do
