common/util/Util/Aeson.hs (88 lines of code) (raw):

-- Copyright (c) Facebook, Inc. and its affiliates. -- FromJSON instance below is an orphan (deliberately) {-# OPTIONS_GHC -Wno-orphans #-} module Util.Aeson ( toJSONText , toJSONByteString , cStringToObject , unsafeCStringToObject , unsafeCStringLenToObject , prettyJSON , parseValueStrict' , parseValueStrict ) where import Data.Aeson import Data.Aeson.Parser (value, value') import Data.Aeson.Types (typeMismatch) import Data.ByteString (ByteString) import qualified Data.Attoparsec.ByteString as A import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.ByteString as B import qualified Data.ByteString.Unsafe as B import qualified Data.ByteString.Lazy as LB import Data.HashMap.Strict (HashMap) import Data.Text (Text) import Foreign.C import Text.PrettyPrint import qualified Data.HashMap.Strict as HashMap import qualified Data.Vector as Vector import qualified Data.Text as Text import qualified Data.Text.Encoding as Text -- | Converts a value to JSON, and returns it as a 'Text' toJSONText :: ToJSON x => x -> Text.Text toJSONText = Text.decodeUtf8 . toJSONByteString -- | Converts a value to JSON, and returns it as a 'ByteString' toJSONByteString :: ToJSON x => x -> ByteString toJSONByteString = LB.toStrict . encode -- | Since Aeson is lazily parsed, this assumes that that the memory -- pointed to in the 'CString' will live throughout the liftime of the -- returned object. If not, this potentially references freed memory. If -- you cannot guarantee the lifetime, use 'cStringToObject'. unsafeCStringToObject :: CString -> IO (Maybe Object) unsafeCStringToObject s = decodeStrict <$> B.unsafePackCString s -- | like 'unsafeCStringToObject' but takes a length too unsafeCStringLenToObject :: CString -> Int -> IO (Maybe Object) unsafeCStringLenToObject s l = decodeStrict <$> B.unsafePackCStringLen (s,l) -- | Copies the 'CString' contents into Haskell, and then creates a -- lazily parsed Aeson 'Object'. cStringToObject :: CString -> IO (Maybe Object) cStringToObject s = decodeStrict <$> B.packCString s -- | Convert a JSON value to a 'String' with indentation to make it -- easier to read. prettyJSON :: Value -> String prettyJSON val = show (pp val) where pp :: Value -> Doc pp Null = text "null" pp (Bool b) = text $ if b then "true" else "false" pp (String txt) = text (Text.unpack $ Text.decodeUtf8 $ LB.toStrict $ encode (String txt)) pp (Number i) = text (show i) pp (Object hm) = braces $ sep $ punctuate (char ',') [ sep [ pp (String key) <+> char ':', nest 2 (pp v) ] | (key, v) <- HashMap.toList hm ] pp (Array arr) = brackets $ sep $ punctuate (char ',') $ map pp $ Vector.toList arr -- eitherDecode family of function strictly requires that the Value is -- either an object or an array. -- This relaxes that restriction and allows other kinds of Values. -- NOTE: this is fixed in latest aeson parseValueStrict :: ByteString -> Either String Value parseValueStrict = A.parseOnly (A.skipSpace *> value <* A.skipSpace <* A.endOfInput) parseValueStrict' :: ByteString -> Either String Value parseValueStrict' = A.parseOnly (A.skipSpace *> value' <* A.skipSpace <* A.endOfInput) -- Orphan instance to make fromJSON O(1) when converting to an Object -- (aka HashMap Text Value). Otherwise the default instances provided -- by Aeson will rebuild the HashMap. -- -- This needs to be not just OVERLAPPABLE, but INCOHERENT, because -- otherwise a constraint "FromJSON (HashMap Text a)" doesn't have a -- single most-specific instance to resolve to. INCOHERENT is ok; -- the worst that can happen is that we lose the optimisation. instance {-# INCOHERENT #-} FromJSON (HashMap Text Value) where parseJSON (Object obj) = return obj parseJSON other = typeMismatch "object" other