module Data.Aeson.Transform
  (
    -- * Example usage
    -- $use

    -- * Transformation builder
    Builder(..)
    -- * Executing transformation
  , transform
  ) where

import Data.Aeson as A
import Data.Text
import qualified Data.Vector as Vec
import qualified Data.HashMap.Strict as H

-- | Transformations are specified by creating 'Builder' instances.
-- Builders specify how to navigate through input JSON and construct
-- output at various nodes in the tree.
--
data Builder =
    Id
  | At Text Builder
  | Attr Text
  | Attrs [Text]
  | Map Builder
  | Index Int Builder
  | Obj (H.HashMap Text Builder)

-- | Generates new Aeson 'Value' guided by a 'Builder'
--
transform :: Builder -> Value -> Value
transform (At k b)    (A.Object o) = transform b $ o H.! k
transform (At _ _)    _            = error "Expecting object (At)"
transform (Index i b) (Array a)    = transform b $ a Vec.! i
transform (Index _ _) _            = error "Expecting array (Index)"
transform (Map b)     (Array a)    = Array $ Vec.map (transform b) a
transform (Map _)     _            = error "Expecting array (Map)"
transform (Attr k)    (A.Object o) = o H.! k
transform (Attr _)    _            = error "Expecting object (Attr)"
transform (Attrs ks)  (A.Object o) = A.Object $ H.filterWithKey (const . (`elem` ks)) o
transform (Attrs _)   _            = error "Expecting object (Attrs)"
transform (Obj fs)    x            = A.Object $ H.map (`transform` x) fs
transform Id          x            = x

-- $use
--
-- Filter unwanted attributes from an object
--
-- > Attrs ["nice", "good"]
-- >
-- > -- { bad: 3, good: 1, nice: 500, evil: -3 }
-- > -- => { good: 1, nice: 500 }
--
-- Grab value
--
-- > Attr "foo"
-- >
-- > -- { foo: 2 } => 2
--
-- Dig deeper
--
-- > At "foo" $ Attr "bar"
-- >
-- > -- { foo: { bar: 3 }} => 3
--
-- Map stuff
--
-- > Map $ Attr "foo"
-- >
-- > -- [{ foo:1, foo:2 }] => [1, 2]
--
-- Extract indices
--
-- > Map $ Index 0 Id
-- >
-- > -- [[1,2], [3,4]] => [1, 3]
--
-- Create object
--
-- > Obj $ fromList [
-- >     ("first", Index 0 Id)
-- >   , ("second", Index 1 Id)
-- >   ]
-- >
-- > -- ["hi", "bye"] => { first:"hi", second:"bye" }