{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} -- | -- Module: Data.Aeson.Prefix -- Maintainer: Jiri Marsicek -- -- Hiearchical prefixing of JSON objects from 'Data.Aeson' -- Please see examples for understanding what does it mean. -- -- == Examples -- -- usage of 'prefix' -- -- === Basic -- -- @{ "a": { "b": 1 } }@ results in @{ "a": { "a.b": 2 }@ -- -- @{ "a": { "b": { "c": 1 } } }@ results in @{ "a": { "a.b": { "a.b.c": 1 } } }@ -- -- === Arrays -- -- @{ "a": [ { "b": 1 }, { "b": 2 } ] }@ is not changed -- -- * Arrays don't inherit the prefix from their parent keys by default -- -- === With 'optionPreservePrefix' set to 'True' -- -- @{ "a": [ { "b": 1 }, { "b": 2 } ] }@ results in @{ "a": [ { "a.b": 1 }, { "a.b": 2 } ] }@ -- -- === With 'optionPrefix' set to "prefix" -- -- @{ "a": 1 }@ results in @{ "prefix.a": 1 }@ -- -- * This affects only keys in top level object. If array is top level, it doesn't take effect -- -- === With 'optionSeparator' set to "~" -- -- @{ "a": { "b": 1 } }@ results in @{ "a": { "a~b": 2 } }@ module Data.Aeson.Prefix ( prefix -- * Options , Options(..) , Prefix , Separator , defaultOptions -- * Utility functions and types , Pair , prefixKey , prefixPair , withPrefix , withoutPrefix ) where import Control.Monad.Reader (MonadReader, asks, local) import Data.Aeson hiding (defaultOptions) import qualified Data.HashMap.Strict as Map (fromList, toList) import Data.Monoid ((<>)) import qualified Data.Vector as Vector (mapM) import Data.Text (Text) import qualified Data.Text as Text (singleton) type Pair = (Text, Value) type Prefix = Maybe Text type Separator = Text data Options = Options { -- | -- Preserve prefix in Arrays, objects in arrays preserve prefix from their parent key optionPreservePrefix :: Bool -- | -- Separator, text to delimit a prefix from a key , optionSeparator :: Separator -- | -- Prefix, prefix added to all keys of top level object , optionPrefix :: Prefix } deriving Show -- | -- Default options -- -- * 'optionPreservePrefix' set to 'False' -- * 'optionSeparator' set to "." -- * 'optionPrefix' set to 'Nothing' defaultOptions :: Options defaultOptions = Options False (Text.singleton '.') Nothing -- | -- Change options to use supplied prefix withPrefix :: Prefix -> Options -> Options withPrefix p o = o { optionPrefix = p } -- | -- Change options to not use any prefix withoutPrefix :: Options -> Options withoutPrefix o = o { optionPrefix = Nothing } -- | -- Prefixes text with prefix if defined in options, a separator from options is used to delimit prefix and text prefixKey :: forall m . (Monad m, MonadReader Options m) => Text -> m Text prefixKey t = do sep <- asks optionSeparator pre <- asks optionPrefix return $ maybe t (\p -> p <> sep <> t) pre -- | -- Prefixes identifier (first in pair), this prefixed identifier is used as a prefix for value (second in pair) prefixPair :: forall m . (Monad m, MonadReader Options m) => Pair -> m Pair prefixPair (i, v) = do pk <- prefixKey i pv <- local (withPrefix $ Just pk) $ prefix v return (pk, pv) -- | -- Convert `Object` to list of `Pair`s objectToPairs :: Object -> [Pair] objectToPairs = Map.toList -- Convert list of `Pair`s to `Object` pairsToObject :: [Pair] -> Object pairsToObject = Map.fromList -- | -- Prefixes supplied `Value` using `Options` prefix :: forall m . (Monad m, MonadReader Options m) => Value -> m Value prefix (Array a) = do preserve <- asks optionPreservePrefix let prefixed = Array <$> Vector.mapM prefix a if preserve then prefixed else local withoutPrefix prefixed prefix (Object o) = do p <- mapM prefixPair $ objectToPairs o return $ Object $ pairsToObject p prefix v = return v