-- |Module which provides 'JsonFormat's for a variety of types from @base@ and other common packages. module Composite.Aeson.Formats.Provided where import Composite.Aeson.Base (JsonFormat(JsonFormat), JsonProfunctor(JsonProfunctor), _JsonProfunctor, dimapJsonFormat, toJsonWithFormat) import Composite.Aeson.Formats.Generic (SumStyle, abeJsonFormat, aesonJsonFormat, jsonArrayFormat, jsonObjectFormat, jsonSumFormat) import Composite.Aeson.Formats.InternalTH (makeTupleFormats) import Control.Arrow (first) import Control.Lens (_2, _Wrapped, over, view) import qualified Data.Aeson as Aeson import qualified Data.Aeson.BetterErrors as ABE import Data.Fixed (HasResolution, Fixed) import Data.Foldable (toList) import Data.Hashable (Hashable) import qualified Data.HashMap.Lazy as LazyHashMap import qualified Data.HashMap.Strict as StrictHashMap import Data.IntSet (IntSet) import Data.List.NonEmpty (NonEmpty((:|))) import qualified Data.List.NonEmpty as NEL import qualified Data.Map.Lazy as LazyMap import qualified Data.Map.Strict as StrictMap import Data.Scientific (Scientific) import qualified Data.Scientific as Scientific import Data.Sequence (Seq) import qualified Data.Sequence as Sequence import Data.Text (Text) import qualified Data.Text.Lazy as LT import qualified Data.Vector as V import Data.Version (Version) import Numeric.Natural (Natural) -- |JSON format for 'Aeson.Array' which maps to any array in JSON. aesonArrayJsonFormat :: JsonFormat e Aeson.Array aesonArrayJsonFormat = abeJsonFormat ABE.asArray -- |JSON format for 'Aeson.Object' which maps to any object in JSON. aesonObjectJsonFormat :: JsonFormat e Aeson.Object aesonObjectJsonFormat = abeJsonFormat ABE.asObject -- |JSON format which does no parsing or encoding. aesonValueJsonFormat :: JsonFormat e Aeson.Value aesonValueJsonFormat = abeJsonFormat ABE.asValue -- |'JsonFormat' for 'Bool', mapping to a JSON boolean. boolJsonFormat :: JsonFormat e Bool boolJsonFormat = abeJsonFormat ABE.asBool -- |'JsonFormat' for 'Char', mapping to a JSON string. charJsonFormat :: JsonFormat e Char charJsonFormat = aesonJsonFormat -- |'JsonFormat' for 'Either' which maps to JSON as an object via 'jsonSumFormat'. eitherJsonFormat :: SumStyle -> Text -> Text -> JsonFormat e a -> JsonFormat e b -> JsonFormat e (Either a b) eitherJsonFormat style leftName rightName leftFormat rightFormat = jsonSumFormat style o is where o = \ case Left a -> (leftName, toJsonWithFormat leftFormat a) Right b -> (rightName, toJsonWithFormat rightFormat b) is = (leftName, Left <$> view (_Wrapped . _JsonProfunctor . _2) leftFormat) :| [(rightName, Right <$> view (_Wrapped . _JsonProfunctor . _2) rightFormat)] -- |'JsonFormat' for 'Fixed' precision real numbers. fixedJsonFormat :: HasResolution r => JsonFormat e (Fixed r) fixedJsonFormat = aesonJsonFormat -- |'JsonFormat' for 'StrictHashMap.HashMap' where the key type can be converted to and from a 'Text', mapping to a JSON object. strictHashMapJsonFormat :: (Eq k, Hashable k) => (k -> Text) -> (Text -> ABE.Parse e k) -> JsonFormat e a -> JsonFormat e (StrictHashMap.HashMap k a) strictHashMapJsonFormat kToText kFromText = jsonObjectFormat (fmap (first kToText) . StrictHashMap.toList) (fmap StrictHashMap.fromList . traverse (\ (k, a) -> (, a) <$> kFromText k)) -- |'JsonFormat' for 'LazyHashMap.HashMap' where the key type can be converted to and from a 'Text', mapping to a JSON object. lazyHashMapJsonFormat :: (Eq k, Hashable k) => (k -> Text) -> (Text -> ABE.Parse e k) -> JsonFormat e a -> JsonFormat e (LazyHashMap.HashMap k a) lazyHashMapJsonFormat kToText kFromText = jsonObjectFormat (fmap (first kToText) . LazyHashMap.toList) (fmap LazyHashMap.fromList . traverse (\ (k, a) -> (, a) <$> kFromText k)) -- |'JsonFormat' for 'IntSet' which maps to an array of numbers. intSetJsonFormat :: JsonFormat e IntSet intSetJsonFormat = aesonJsonFormat -- |Polymorphic JSON format for any type which instances 'Integral'. integralJsonFormat :: Integral a => JsonFormat e a integralJsonFormat = JsonFormat $ JsonProfunctor (Aeson.Number . fromIntegral) ABE.asIntegral -- |'JsonFormat' for 'Data.Text.Lazy.Text'. lazyTextJsonFormat :: JsonFormat e LT.Text lazyTextJsonFormat = dimapJsonFormat LT.toStrict LT.fromStrict textJsonFormat -- |'JsonFormat' for '[]' which maps to a JSON array. listJsonFormat :: JsonFormat e a -> JsonFormat e [a] listJsonFormat = jsonArrayFormat id pure -- |'JsonFormat' for 'StrictMap.Map' where the key type can be converted to and from a 'Text', mapping to a JSON object. strictMapJsonFormat :: Ord k => (k -> Text) -> (Text -> ABE.Parse e k) -> JsonFormat e a -> JsonFormat e (StrictMap.Map k a) strictMapJsonFormat kToText kFromText = jsonObjectFormat (fmap (first kToText) . StrictMap.toAscList) (fmap StrictMap.fromList . traverse (\ (k, a) -> (, a) <$> kFromText k)) -- |'JsonFormat' for 'LazyMap.Map' where the key type can be converted to and from a 'Text', mapping to a JSON object. lazyMapJsonFormat :: Ord k => (k -> Text) -> (Text -> ABE.Parse e k) -> JsonFormat e a -> JsonFormat e (LazyMap.Map k a) lazyMapJsonFormat kToText kFromText = jsonObjectFormat (fmap (first kToText) . LazyMap.toAscList) (fmap LazyMap.fromList . traverse (\ (k, a) -> (, a) <$> kFromText k)) -- |'JsonFormat' for 'Maybe' which maps @Nothing@ to @null@. maybeJsonFormat :: JsonFormat e a -> JsonFormat e (Maybe a) maybeJsonFormat = over _Wrapped $ \ (JsonProfunctor o i) -> JsonProfunctor (maybe Aeson.Null o) (ABE.perhaps i) -- |'JsonFormat' for 'Natural' numbers. naturalJsonFormat :: JsonFormat e Natural naturalJsonFormat = aesonJsonFormat -- |'JsonFormat' for 'NonEmpty' which maps to a JSON array. nonEmptyListJsonFormat :: JsonFormat e a -> JsonFormat e (NonEmpty a) nonEmptyListJsonFormat = jsonArrayFormat NEL.toList (maybe (fail "expected nonempty array") pure . NEL.nonEmpty) -- |JSON format for '()' which maps to JSON as @null@. nullJsonFormat :: JsonFormat e () nullJsonFormat = abeJsonFormat ABE.asNull -- |JSON format for 'Ordering' which maps to the strings @LT@, @GT@, and @EQ@ orderingJsonFormat :: JsonFormat e Ordering orderingJsonFormat = aesonJsonFormat -- |Polymorphic JSON format for any type which instances 'RealFloat'. See warning in documentation for 'scientificJsonFormat' about scientific notation. realFloatJsonFormat :: RealFloat a => JsonFormat e a realFloatJsonFormat = JsonFormat $ JsonProfunctor realFloatToJson ABE.asRealFloat -- |Convert some 'RealFloat' value to 'Aeson.Value'. Copied from Aeson internals which do not export it. realFloatToJson :: RealFloat a => a -> Aeson.Value realFloatToJson d | isNaN d || isInfinite d = Aeson.Null | otherwise = Aeson.Number $ Scientific.fromFloatDigits d {-# INLINE realFloatToJson #-} -- |'JsonFormat' for 'Scientific', mapping to a JSON number. -- -- __Warning:__ some JSON parsing libraries do not accept the scientific number notation even though it's part of the JSON standard, and this format -- uses 'Data.ByteString.Builder.Scientific.scientificBuilder' transitively which encodes very small (< 0.1) and large (> 9,999,999.0) fractional numbers -- using scientific notation. scientificJsonFormat :: JsonFormat e Scientific scientificJsonFormat = abeJsonFormat ABE.asScientific -- |'JsonFormat' for 'Seq'. seqJsonFormat :: JsonFormat e a -> JsonFormat e (Seq a) seqJsonFormat = jsonArrayFormat toList (pure . Sequence.fromList) -- |'JsonFormat' for 'String'. stringJsonFormat :: JsonFormat e String stringJsonFormat = abeJsonFormat ABE.asString -- |'JsonFormat' for arbitrary sum types which maps to JSON as an object with fields determined by the 'SumStyle' chosen. See 'SumStyle' for more information -- about the various styles. -- |'JsonFormat' for 'Text'. textJsonFormat :: JsonFormat e Text textJsonFormat = abeJsonFormat ABE.asText $makeTupleFormats -- |'JsonFormat' for '()' which maps to an empty array. unitJsonFormat :: JsonFormat e () unitJsonFormat = aesonJsonFormat -- |'JsonFormat' for 'Vector's which map to an array. vectorJsonFormat :: JsonFormat e a -> JsonFormat e (V.Vector a) vectorJsonFormat (JsonFormat (JsonProfunctor oA iA)) = JsonFormat (JsonProfunctor o i) where o = Aeson.Array . fmap oA i = V.fromList <$> ABE.eachInArray iA -- |'JsonFormat' for 'Version' which maps to a string. versionJsonFormat :: JsonFormat e Version versionJsonFormat = aesonJsonFormat