{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DeriveDataTypeable #-} --------------------------------------------------------- -- -- Module : Data.Object.Json -- Copyright : Michael Snoyman -- License : BSD3 -- -- Maintainer : Michael Snoyman -- Stability : Stable -- Portability : portable -- | A simple wrapper around the json-b library which presents values inside -- 'Object's. module Data.Object.Json ( -- * Definition of 'JsonObject' JsonScalar (..) , JsonObject -- * Automatic scalar conversions , IsJsonScalar (..) , toJsonObject , fromJsonObject -- * Encoding/decoding , encode , encodeFile , decode , decodeFile -- * Exception type , JsonDecodeError ) where import Data.Object import qualified Data.Trie import Control.Arrow import Data.Data import Control.Exception import Data.String (IsString (fromString)) import Text.JSONb.Simple as J import qualified Text.JSONb.Decode as Decode import qualified Text.JSONb.Encode as Encode import qualified Data.Text import qualified Data.Text.Lazy import qualified Data.ByteString import qualified Data.ByteString.Lazy import Data.Convertible.Text (ConvertSuccess, cs) import Control.Failure -- | Matches the scalar data types used in json-b so we can have proper mapping -- between the two libraries. data JsonScalar = JsonString Data.ByteString.ByteString | JsonNumber Rational | JsonBoolean Bool | JsonNull deriving (Eq, Show, Read, Data, Typeable) instance IsString JsonScalar where fromString = toJsonScalar jsToBS :: JsonScalar -> Data.ByteString.ByteString jsToBS (JsonString b) = b jsToBS (JsonNumber r) = cs r jsToBS (JsonBoolean b) = cs b jsToBS JsonNull = Data.ByteString.empty class (Eq a) => IsJsonScalar a where fromJsonScalar :: JsonScalar -> a toJsonScalar :: a -> JsonScalar instance IsJsonScalar JsonScalar where fromJsonScalar = id toJsonScalar = id instance IsJsonScalar Data.Text.Text where fromJsonScalar = cs . jsToBS toJsonScalar = JsonString . cs instance IsJsonScalar Data.Text.Lazy.Text where fromJsonScalar = cs . jsToBS toJsonScalar = JsonString . cs instance IsJsonScalar [Char] where fromJsonScalar = cs . jsToBS toJsonScalar = JsonString . cs instance IsJsonScalar Data.ByteString.ByteString where fromJsonScalar = jsToBS toJsonScalar = JsonString instance IsJsonScalar Data.ByteString.Lazy.ByteString where fromJsonScalar = cs . jsToBS toJsonScalar = JsonString . cs toJsonObject :: ConvertSuccess k Data.ByteString.ByteString => IsJsonScalar v => Object k v -> JsonObject toJsonObject = mapKeysValues cs toJsonScalar fromJsonObject :: ConvertSuccess Data.ByteString.ByteString k => IsJsonScalar v => JsonObject -> Object k v fromJsonObject = mapKeysValues cs fromJsonScalar -- | Meant to match closely with the 'JSON' data type. Therefore, uses strict -- byte strings for keys and the 'JsonScalar' type for scalars. type JsonObject = Object Data.ByteString.ByteString JsonScalar jsonToJO :: JSON -> JsonObject jsonToJO (J.Object trie) = Mapping . map (second jsonToJO) $ Data.Trie.toList trie jsonToJO (J.Array a) = Sequence $ map jsonToJO $ a jsonToJO (J.String bs) = Scalar $ JsonString bs jsonToJO (J.Number r) = Scalar $ JsonNumber r jsonToJO (J.Boolean b) = Scalar $ JsonBoolean b jsonToJO J.Null = Scalar JsonNull joToJSON :: JsonObject -> JSON joToJSON (Scalar (JsonString bs)) = J.String bs joToJSON (Scalar (JsonNumber r)) = J.Number r joToJSON (Scalar (JsonBoolean b)) = J.Boolean b joToJSON (Scalar JsonNull) = J.Null joToJSON (Sequence s) = J.Array $ map joToJSON s joToJSON (Mapping m) = J.Object $ Data.Trie.fromList $ map (second joToJSON) m -- | Error type for JSON decoding errors. newtype JsonDecodeError = JsonDecodeError String deriving (Show, Typeable) instance Exception JsonDecodeError -- | Decode a lazy bytestring into a 'JsonObject'. decode :: Failure JsonDecodeError m => ConvertSuccess Data.ByteString.ByteString k => IsJsonScalar v => Data.ByteString.ByteString -> m (Object k v) decode = either (failure . JsonDecodeError) (return . fromJsonObject . jsonToJO) . Decode.decode -- | Encode a 'JsonObject' into a lazy bytestring. encode :: (ConvertSuccess k Data.ByteString.ByteString, IsJsonScalar v) => Object k v -> Data.ByteString.ByteString encode = Encode.encode Encode.Compact . joToJSON . toJsonObject encodeFile :: (ConvertSuccess k Data.ByteString.ByteString, IsJsonScalar v) => FilePath -> Object k v -> IO () encodeFile fp = Data.ByteString.writeFile fp . encode decodeFile :: Failure JsonDecodeError m => ConvertSuccess Data.ByteString.ByteString k => IsJsonScalar v => FilePath -> IO (m (Object k v)) decodeFile fp = decode `fmap` Data.ByteString.readFile fp