@@ -36,13 +36,18 @@ module Data.Aeson.Types.Internal
3636 , defaultOptions
3737 , defaultTaggedObject
3838
39+ -- * Used for changing CamelCase names into something else.
40+ , camelTo
41+
3942 -- * Other types
4043 , DotNetTime(..)
4144 ) where
4245
46+
4347import Control.Applicative
4448import Control.Monad
4549import Control.DeepSeq (NFData(..))
50+import Data.Char (toLower, isUpper)
4651import Data.Scientific (Scientific)
4752import Data.Hashable (Hashable(..))
4853import Data.Data (Data)
@@ -346,3 +351,25 @@ defaultTaggedObject = TaggedObject
346351 { tagFieldName = "tag"
347352 , contentsFieldName = "contents"
348353 }
354+
355+-- | Converts from CamelCase to another lower case, interspersing
356+-- the character between all capital letters and their previous
357+-- entries, except those capital letters that appear together,
358+-- like 'API'.
359+--
360+-- For use by Aeson template haskell calls.
361+--
362+-- > camelTo '_' 'CamelCaseAPI' == "camel_case_api"
363+camelTo :: Char -> String -> String
364+camelTo c = lastWasCap True
365+ where
366+ lastWasCap :: Bool -- ^ Previous was a capital letter
367+ -> String -- ^ The remaining string
368+ -> String
369+ lastWasCap _ [] = []
370+ lastWasCap prev (x : xs) = if isUpper x
371+ then if prev
372+ then toLower x : lastWasCap True xs
373+ else c : toLower x : lastWasCap True xs
374+ else x : lastWasCap False xs
375+