module Data.Aeson.OneLine
    ( renderValue
    , renderObject
    , renderArray
    ) where

-- aeson
import qualified Data.Aeson.Text        as Aeson
import qualified Data.Aeson.Types       as Aeson
import qualified Data.Aeson.KeyMap      as Aeson.KeyMap

-- base
import qualified Data.Foldable          as Foldable
import           Data.Function          (on)
import           Data.List              (sortBy)
import           Prelude                hiding ((+))

-- text
import           Data.Text              (Text)
import qualified Data.Text              as Text
import qualified Data.Text.Lazy         as LText
import qualified Data.Text.Lazy.Builder as LText

(+) :: Text -> Text -> Text
+ :: Text -> Text -> Text
(+) = Text -> Text -> Text
Text.append

commaSeparate :: [Text] -> Text
commaSeparate :: [Text] -> Text
commaSeparate = Text -> [Text] -> Text
Text.intercalate (String -> Text
Text.pack String
", ")

-- | Show an aeson value is a one-line format with a single
-- space after each comma and colon, which should be suitable
-- for human reading as long as the value isn't too large.
--
-- >>> import Data.Aeson
--
-- >>> :{
-- >>> val = object [ Text.pack "name" .= Text.pack "Alonzo"
-- >>>              , Text.pack "age" .= 3 ]
-- >>> :}
--
-- >>> (putStrLn . Text.unpack . renderValue) val
-- {"age": 3, "name": "Alonzo"}

renderValue :: Aeson.Value -> Text
renderValue :: Value -> Text
renderValue Value
val =
    case Value
val of
        -- For objects and arrays, we customize the rendering.
        Aeson.Object Object
x -> Object -> Text
renderObject Object
x
        Aeson.Array  Array
x -> Array -> Text
renderArray Array
x

        -- For the rest of the constructors, we
        -- render the value just like Aeson does.
        Value
x              -> Value -> Text
renderTerse Value
x

-- | Show an aeson value the way the aeson library does it, in a
-- terse style not intended for human reading.

renderTerse :: Aeson.Value -> Text
renderTerse :: Value -> Text
renderTerse =
    Text -> Text
LText.toStrict forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Text
LText.toLazyText forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToJSON a => a -> Builder
Aeson.encodeToTextBuilder

renderObject :: Aeson.Object -> Text
renderObject :: Object -> Text
renderObject Object
obj =
    String -> Text
Text.pack String
"{" Text -> Text -> Text
+ Text
x Text -> Text -> Text
+ String -> Text
Text.pack String
"}"
    where
        x :: Text
        x :: Text
x = [Text] -> Text
commaSeparate ((Key, Value) -> Text
f forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object -> [(Key, Value)]
objectToListAsc Object
obj)

        f :: (Aeson.Key, Aeson.Value) -> Text
        f :: (Key, Value) -> Text
f (Key
k, Value
v) = Value -> Text
renderTerse (forall a. ToJSON a => a -> Value
Aeson.toJSON Key
k) Text -> Text -> Text
+
                   String -> Text
Text.pack String
": " Text -> Text -> Text
+ Value -> Text
renderValue Value
v

renderArray :: Aeson.Array -> Text
renderArray :: Array -> Text
renderArray Array
arr =
    String -> Text
Text.pack String
"[" Text -> Text -> Text
+ Text
x Text -> Text -> Text
+ String -> Text
Text.pack String
"]"
    where
        x :: Text
x = [Text] -> Text
commaSeparate (Value -> Text
renderValue forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (t :: * -> *) a. Foldable t => t a -> [a]
Foldable.toList Array
arr)

objectToListAsc :: Aeson.Object -> [(Aeson.Key, Aeson.Value)]
objectToListAsc :: Object -> [(Key, Value)]
objectToListAsc = forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy (forall a. Ord a => a -> a -> Ordering
compare forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` forall a b. (a, b) -> a
fst) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall v. KeyMap v -> [(Key, v)]
Aeson.KeyMap.toList