packages feed

sugar-json-0.0.0: src/Sugar/Json.hs

{-# LANGUAGE TupleSections, DeriveGeneric, OverloadedStrings, CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Sugar.Json
  ( SugarCube(..)
  , sugarCubeMay
  , writeJsonAsSugarBinary
  , writeJsonAsSugar
  ) where

import Data.Text (Text)
import Data.Map (Map)
import Data.Scientific (floatingOrInteger)
import TextShow (showt)

import qualified Data.Map as Map

#if MIN_VERSION_aeson(2,0,0)
import qualified Data.Aeson.Key as AesonKey
import qualified Data.Aeson.KeyMap as KeyMap
#else
import qualified Data.HashMap.Strict as KeyMap
#endif

import qualified Data.Serialize as Serialize
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.Aeson as Json
import qualified Data.Vector as V
import qualified Data.Text.IO as TIO

import Sugar

-- SugarCube is a refined type of Sugar.
-- This is a useful interface when a Json-like format is easier.
-- Differences:
-- * No notes
-- * No list wrap
-- * Maps are string-key value pairs
data SugarCube
  = SugarCube'Unit
  | SugarCube'Text Text
  | SugarCube'List [SugarCube]
  | SugarCube'Map (Map Text SugarCube)
  deriving (Eq, Show)

class ToSugarCube a where
  toSugarCube :: a -> SugarCube

sugarCubeMay :: Sugar -> Maybe SugarCube
sugarCubeMay (Sugar'Unit _) = Just SugarCube'Unit
sugarCubeMay (Sugar'Text t _) = Just $ SugarCube'Text t
sugarCubeMay (Sugar'List xs _ _) = do
  xs' <- mapM sugarCubeMay xs
  return $ SugarCube'List xs'
sugarCubeMay (Sugar'Map xs _) = do
  xs' <- mapM (\(k,v) -> (,) <$> sugarTextMay k <*> sugarCubeMay v) xs
  return $ SugarCube'Map (Map.fromList xs')

  
writeJsonAsSugarBinary :: FilePath -> FilePath -> IO ()
writeJsonAsSugarBinary src des = do
  bsl <- BL.readFile src
  let value' = Json.decode' bsl :: Maybe Sugar
  case value' of
    Nothing -> putStrLn "Can not decode"
    Just sugar ->  BS.writeFile des $ Serialize.encode sugar

writeJsonAsSugar :: FilePath -> FilePath -> IO ()
writeJsonAsSugar src des = do
  bsl <- BL.readFile src
  let value' = Json.decode' bsl :: Maybe Sugar
  case value' of
    Nothing -> putStrLn "Can not decode"
    Just sugar ->  TIO.writeFile des $ prettyPrintSugar sugar


instance ToSugar SugarCube where
  toSugar SugarCube'Unit = Sugar'Unit Nothing
  toSugar (SugarCube'Text t) = Sugar'Text t Nothing
  toSugar (SugarCube'List xs) = Sugar'List (map (toSugarWithWrap Wrap'Paren) xs) Wrap'Square Nothing
    where
      -- Alternate nesting between Wrap types
      toSugarWithWrap w c = case c of
        SugarCube'List ys -> Sugar'List (map (toSugarWithWrap (case w of Wrap'Square -> Wrap'Paren; Wrap'Paren -> Wrap'Square)) ys) w Nothing
        _ -> toSugar c
  toSugar (SugarCube'Map m) = Sugar'Map (map (\(k,v) -> (toSugar k, toSugar v)) $ Map.toList m) Nothing

instance ToSugarCube Json.Value where
  toSugarCube Json.Null = SugarCube'Unit
  toSugarCube (Json.Bool b) = SugarCube'Text (showt b)
  toSugarCube (Json.String t) = SugarCube'Text t
  toSugarCube (Json.Number n) = SugarCube'Text (showNumber n)
    where
      showNumber s = either showt showt $ (floatingOrInteger s :: Either Double Integer)
  toSugarCube (Json.Array a) = SugarCube'List $ map toSugarCube (V.toList a)
  toSugarCube (Json.Object o) = SugarCube'Map . Map.fromList . map (\(k,v) -> (keyText k, toSugarCube v)) . KeyMap.toList $ o
    where
#if MIN_VERSION_aeson(2,0,0)
      keyText = AesonKey.toText
#else
      keyText = id
#endif
  
instance Json.FromJSON SugarCube where
  parseJSON v = pure $ toSugarCube v
  
instance Json.FromJSON Sugar where
  parseJSON v = pure . toSugar . toSugarCube $ v