jordan-0.1.0.0: lib/Jordan/ToJSON/Text.hs
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Jordan.ToJSON.Text
where
import Data.Functor.Contravariant
import Data.Functor.Contravariant.Divisible
import Data.List (intersperse)
import qualified Data.Scientific as Sci
import Data.Semigroup (Endo(..))
import Data.String (IsString(..))
import qualified Data.Text as T
import Data.Void (absurd)
import Jordan.ToJSON.Class
data TextComma
= Written (T.Text -> T.Text)
| Empty
runWritten :: TextComma -> T.Text -> T.Text
runWritten Empty = id
runWritten (Written f) = f
instance IsString TextComma where
fromString s = Written (<> T.pack s)
instance Semigroup TextComma where
Empty <> a = a
a <> Empty = a
(Written f) <> (Written f') = Written (f . (", " <>) . f')
instance Monoid TextComma where
mempty = Empty
newtype CommaBuilder v = CommaBuilder { runCommaBuilder :: v -> TextComma }
deriving (Semigroup, Monoid) via (v -> TextComma)
runCommaBuilder' :: CommaBuilder v -> v -> T.Text -> T.Text
runCommaBuilder' (CommaBuilder f) = runWritten . f
instance Contravariant CommaBuilder where
contramap f (CommaBuilder v) = CommaBuilder (v . f)
instance Divisible CommaBuilder where
conquer = CommaBuilder (const Empty)
divide d (CommaBuilder b) (CommaBuilder c) = CommaBuilder $ \a ->
let (b', c') = d a in b b' <> c c'
newtype TextArray v = TextArray { runTextArray :: v -> ([T.Text] -> [T.Text]) }
deriving (Semigroup, Monoid) via (v -> Endo [T.Text])
instance Contravariant TextArray where
contramap f (TextArray b) = TextArray $ \a -> b (f a)
instance Divisible TextArray where
conquer = TextArray $ const mempty
divide d (TextArray b) (TextArray c) = TextArray $ \a ->
let (b', c') = d a in b b' . c c'
instance Decidable TextArray where
lose _ = TextArray $ const mempty
choose f (TextArray b) (TextArray c) = TextArray $ \a ->
case f a of
Left b' -> b b'
Right c' -> c c'
instance JSONTupleSerializer CommaBuilder where
writeItem f = CommaBuilder $ Written . runJSONText f
instance JSONObjectSerializer CommaBuilder where
writeField t s = CommaBuilder $ \arg ->
Written (quoteString t . (": " <> ) . runJSONText s arg)
instance JSONTupleSerializer TextArray where
writeItem f = TextArray $ \a -> ([runJSONText f a ""] <>)
newtype JSONText a
= JSONText { runJSONText :: a -> (T.Text -> T.Text) }
deriving (Semigroup, Monoid) via (a -> Endo T.Text)
instance Contravariant JSONText where
contramap f (JSONText s) = JSONText (s . f)
instance Selectable JSONText where
giveUp f = JSONText $ \a -> absurd (f a)
select f (JSONText lhs) (JSONText rhs) = JSONText $ either lhs rhs . f
convChar :: Char -> (T.Text -> T.Text)
convChar = \case
'\b' -> ("\\b" <>)
'\f' -> ("\\f" <>)
'\n' -> ("\\n" <>)
'\r' -> ("\\r" <>)
'\t' -> ("\\t" <>)
'"' -> ("\\\"" <>)
'\\' -> ("\\\\" <>)
o -> (T.singleton o <>)
isBadChar :: Char -> Bool
isBadChar = \case
'\b' -> True
'\f' -> True
'\r' -> True
'\t' -> True
'\\' -> True
'"' -> True
_ -> False
quoteString :: T.Text -> (T.Text -> T.Text)
quoteString t = ("\"" <>) . innerText . ("\"" <>)
where
innerText
| T.any isBadChar t = T.foldl' (\o a -> o . convChar a) id t
| otherwise = (t <>)
sArray :: (a -> T.Text -> T.Text) -> [a] -> (T.Text -> T.Text)
sArray _ [] = id
sArray f [x] = f x
sArray f (x : xs) = f x . ("," <>) . sArray f xs
instance JSONSerializer JSONText where
serializeNull = JSONText $ const ("null" <>)
serializeText = JSONText $ \a -> quoteString a
serializeTextConstant t = JSONText $ const $ quoteString t
serializeNumber = JSONText $ \n ->
((T.pack $ Sci.formatScientific Sci.Generic Nothing n) <>)
serializeDictionary (JSONText serItem) = JSONText $ \n ->
("{" <>) . keys n . ("}" <>)
where
keys v = runWritten $ foldMap (\(k, v) -> Written $ quoteString k . (": " <> ) . serItem v) v
serializeBool = JSONText $ \a -> ((if a then "true" else "false") <>)
serializeObject n obj = JSONText $ \arg ->
("{" <>) . runCommaBuilder' obj arg . ("}" <>)
serializeTuple obj = JSONText $ \arg ->
("[" <>) . runCommaBuilder' obj arg . ("}" <>)
serializeArray = JSONText $ \a -> ("[" <>) . sArray (runJSONText toJSON) a . (<> "]")
toJSONText :: (ToJSON a) => a -> T.Text
toJSONText a = runJSONText toJSON a ""