web: better ToJSON instances, new FromJSON instances, utilities (#316)
This commit is contained in:
		
							parent
							
								
									782e6d3272
								
							
						
					
					
						commit
						b46212ae50
					
				| @ -20,13 +20,12 @@ module Hledger.Web.Handler.MiscR | |||||||
|   , getRootR |   , getRootR | ||||||
|   ) where |   ) where | ||||||
| 
 | 
 | ||||||
| import           Data.Aeson |  | ||||||
| import           Data.Decimal |  | ||||||
| import qualified Data.Map as M | import qualified Data.Map as M | ||||||
| import qualified Data.Text as T | import qualified Data.Text as T | ||||||
| import Yesod.Default.Handlers (getFaviconR, getRobotsR) | import Yesod.Default.Handlers (getFaviconR, getRobotsR) | ||||||
| 
 | 
 | ||||||
| import Hledger | import Hledger | ||||||
|  | import Hledger.Web.Json () | ||||||
| import Hledger.Web.Import | import Hledger.Web.Import | ||||||
| import Hledger.Web.Widget.Common (journalFile404) | import Hledger.Web.Widget.Common (journalFile404) | ||||||
| 
 | 
 | ||||||
| @ -49,47 +48,7 @@ getDownloadR f = do | |||||||
|   addHeader "Content-Disposition" ("attachment; filename=\"" <> T.pack f' <> "\"") |   addHeader "Content-Disposition" ("attachment; filename=\"" <> T.pack f' <> "\"") | ||||||
|   sendResponse ("text/plain" :: ByteString, toContent txt) |   sendResponse ("text/plain" :: ByteString, toContent txt) | ||||||
| 
 | 
 | ||||||
| -- copied from hledger-api | -- hledger-web equivalents of hledger-api's handlers | ||||||
| instance ToJSON Status |  | ||||||
| instance ToJSON GenericSourcePos |  | ||||||
| instance ToJSON Decimal where toJSON = toJSON . show |  | ||||||
| instance ToJSON Amount |  | ||||||
| instance ToJSON AmountStyle |  | ||||||
| instance ToJSON Side |  | ||||||
| instance ToJSON DigitGroupStyle |  | ||||||
| instance ToJSON MixedAmount |  | ||||||
| instance ToJSON BalanceAssertion |  | ||||||
| instance ToJSON Price |  | ||||||
| instance ToJSON MarketPrice |  | ||||||
| instance ToJSON PostingType |  | ||||||
| instance ToJSON Posting where |  | ||||||
|   toJSON Posting{..} = |  | ||||||
|     object |  | ||||||
|     ["pdate"             .= toJSON pdate |  | ||||||
|     ,"pdate2"            .= toJSON pdate2 |  | ||||||
|     ,"pstatus"           .= toJSON pstatus |  | ||||||
|     ,"paccount"          .= toJSON paccount |  | ||||||
|     ,"pamount"           .= toJSON pamount |  | ||||||
|     ,"pcomment"          .= toJSON pcomment |  | ||||||
|     ,"ptype"             .= toJSON ptype |  | ||||||
|     ,"ptags"             .= toJSON ptags |  | ||||||
|     ,"pbalanceassertion" .= toJSON pbalanceassertion |  | ||||||
|     ,"ptransactionidx"   .= toJSON (maybe "" (show.tindex) ptransaction) |  | ||||||
|     ] |  | ||||||
| instance ToJSON Transaction |  | ||||||
| instance ToJSON Account where |  | ||||||
|   toJSON a = |  | ||||||
|     object |  | ||||||
|     ["aname"        .= toJSON (aname a) |  | ||||||
|     ,"aebalance"    .= toJSON (aebalance a) |  | ||||||
|     ,"aibalance"    .= toJSON (aibalance a) |  | ||||||
|     ,"anumpostings" .= toJSON (anumpostings a) |  | ||||||
|     ,"aboring"      .= toJSON (aboring a) |  | ||||||
|     ,"aparentname"  .= toJSON (maybe "" aname $ aparent a) |  | ||||||
|     ,"asubs"        .= toJSON (map toJSON $ asubs a) |  | ||||||
|     ] |  | ||||||
| 
 |  | ||||||
| -- hledger-web implementations of hledger-api's handlers, keep synced |  | ||||||
| 
 | 
 | ||||||
| getAccountnamesR :: Handler TypedContent | getAccountnamesR :: Handler TypedContent | ||||||
| getAccountnamesR = do | getAccountnamesR = do | ||||||
|  | |||||||
							
								
								
									
										145
									
								
								hledger-web/Hledger/Web/Json.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								hledger-web/Hledger/Web/Json.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,145 @@ | |||||||
|  | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||||||
|  | 
 | ||||||
|  | --{-# LANGUAGE CPP                 #-} | ||||||
|  | --{-# LANGUAGE DataKinds           #-} | ||||||
|  | --{-# LANGUAGE DeriveAnyClass      #-} | ||||||
|  | {-# LANGUAGE DeriveGeneric       #-} | ||||||
|  | --{-# LANGUAGE FlexibleContexts #-} | ||||||
|  | {-# LANGUAGE FlexibleInstances   #-} | ||||||
|  | --{-# LANGUAGE NamedFieldPuns #-} | ||||||
|  | --{-# LANGUAGE OverloadedStrings   #-} | ||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | --{-# LANGUAGE PolyKinds           #-} | ||||||
|  | --{-# LANGUAGE QuasiQuotes         #-} | ||||||
|  | --{-# LANGUAGE QuasiQuotes #-} | ||||||
|  | --{-# LANGUAGE Rank2Types #-} | ||||||
|  | --{-# LANGUAGE RankNTypes #-} | ||||||
|  | {-# LANGUAGE RecordWildCards #-} | ||||||
|  | --{-# LANGUAGE ScopedTypeVariables #-} | ||||||
|  | {-# LANGUAGE StandaloneDeriving #-} | ||||||
|  | --{-# LANGUAGE TemplateHaskell       #-} | ||||||
|  | --{-# LANGUAGE TypeFamilies        #-} | ||||||
|  | --{-# LANGUAGE TypeOperators       #-} | ||||||
|  | 
 | ||||||
|  | module Hledger.Web.Json (  | ||||||
|  |   -- * Instances | ||||||
|  |   -- * Utilities | ||||||
|  |    readJsonFile | ||||||
|  |   ,writeJsonFile | ||||||
|  | ) where | ||||||
|  | 
 | ||||||
|  | import           Data.Aeson | ||||||
|  | --import           Data.Aeson.TH | ||||||
|  | import qualified Data.ByteString.Lazy as BL | ||||||
|  | import           Data.Decimal | ||||||
|  | import           Data.Maybe | ||||||
|  | import           GHC.Generics (Generic) | ||||||
|  | 
 | ||||||
|  | import           Hledger.Data | ||||||
|  | 
 | ||||||
|  | -- JSON instances. See also hledger-api. | ||||||
|  | -- Should they be in hledger-lib Types.hs ? | ||||||
|  | 
 | ||||||
|  | -- To JSON | ||||||
|  | 
 | ||||||
|  | instance ToJSON Status | ||||||
|  | instance ToJSON GenericSourcePos | ||||||
|  | instance ToJSON Decimal | ||||||
|  | instance ToJSON Amount | ||||||
|  | instance ToJSON AmountStyle | ||||||
|  | instance ToJSON Side | ||||||
|  | instance ToJSON DigitGroupStyle | ||||||
|  | instance ToJSON MixedAmount | ||||||
|  | instance ToJSON BalanceAssertion | ||||||
|  | instance ToJSON Price | ||||||
|  | instance ToJSON MarketPrice | ||||||
|  | instance ToJSON PostingType | ||||||
|  | instance ToJSON Posting where | ||||||
|  |   toJSON Posting{..} = object | ||||||
|  |     ["pdate"             .= toJSON pdate | ||||||
|  |     ,"pdate2"            .= toJSON pdate2 | ||||||
|  |     ,"pstatus"           .= toJSON pstatus | ||||||
|  |     ,"paccount"          .= toJSON paccount | ||||||
|  |     ,"pamount"           .= toJSON pamount | ||||||
|  |     ,"pcomment"          .= toJSON pcomment | ||||||
|  |     ,"ptype"             .= toJSON ptype | ||||||
|  |     ,"ptags"             .= toJSON ptags | ||||||
|  |     ,"pbalanceassertion" .= toJSON pbalanceassertion | ||||||
|  |     -- To avoid a cycle, show just the parent transaction's index number  | ||||||
|  |     -- in a dummy field. When re-parsed, there will be no parent. | ||||||
|  |     ,"ptransaction_"     .= toJSON (maybe "" (show.tindex) ptransaction) | ||||||
|  |     -- This is probably not wanted in json, we discard it. | ||||||
|  |     ,"porigin"           .= toJSON (Nothing :: Maybe Posting)  | ||||||
|  |     ] | ||||||
|  | instance ToJSON Transaction | ||||||
|  | instance ToJSON Account where | ||||||
|  |   toJSON a = object | ||||||
|  |     ["aname"        .= toJSON (aname a) | ||||||
|  |     ,"aebalance"    .= toJSON (aebalance a) | ||||||
|  |     ,"aibalance"    .= toJSON (aibalance a) | ||||||
|  |     ,"anumpostings" .= toJSON (anumpostings a) | ||||||
|  |     ,"aboring"      .= toJSON (aboring a) | ||||||
|  |     -- To avoid a cycle, show just the parent account's name  | ||||||
|  |     -- in a dummy field. When re-parsed, there will be no parent. | ||||||
|  |     ,"aparent_"     .= toJSON (maybe "" aname $ aparent a) | ||||||
|  |     -- To avoid a cycle, drop the subaccounts, showing just their names | ||||||
|  |     -- in a dummy field. When re-parsed, there will be no subaccounts. | ||||||
|  |     ,"asubs"        .= toJSON ([]::[Account]) | ||||||
|  |     ,"asubs_"       .= toJSON (map aname $ asubs a) | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  | -- From JSON | ||||||
|  | 
 | ||||||
|  | instance FromJSON Status | ||||||
|  | instance FromJSON GenericSourcePos | ||||||
|  | -- | ||||||
|  | -- Decimal | ||||||
|  | -- | ||||||
|  | -- https://stackoverflow.com/questions/40331851/haskell-data-decimal-as-aeson-type | ||||||
|  | ----instance FromJSON Decimal where parseJSON =  | ||||||
|  | ----  A.withScientific "Decimal" (return . right . eitherFromRational . toRational) | ||||||
|  | -- | ||||||
|  | -- https://github.com/PaulJohnson/Haskell-Decimal/issues/6 | ||||||
|  | --deriving instance Generic Decimal | ||||||
|  | --instance FromJSON Decimal | ||||||
|  | deriving instance Generic (DecimalRaw Integer) | ||||||
|  | instance FromJSON (DecimalRaw Integer) | ||||||
|  | -- | ||||||
|  | -- https://github.com/bos/aeson/issues/474 | ||||||
|  | -- http://hackage.haskell.org/package/aeson-1.4.2.0/docs/Data-Aeson-TH.html | ||||||
|  | -- $(deriveFromJSON defaultOptions ''Decimal) -- doesn't work | ||||||
|  | -- $(deriveFromJSON defaultOptions ''DecimalRaw)  -- works; requires TH, but gives better parse error messages | ||||||
|  | -- | ||||||
|  | instance FromJSON Amount | ||||||
|  | instance FromJSON AmountStyle | ||||||
|  | instance FromJSON Side | ||||||
|  | instance FromJSON DigitGroupStyle | ||||||
|  | instance FromJSON MixedAmount | ||||||
|  | instance FromJSON BalanceAssertion | ||||||
|  | instance FromJSON Price | ||||||
|  | instance FromJSON MarketPrice | ||||||
|  | instance FromJSON PostingType | ||||||
|  | instance FromJSON Posting | ||||||
|  | instance FromJSON Transaction | ||||||
|  | instance FromJSON AccountDeclarationInfo | ||||||
|  | instance FromJSON Account | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- Utilities | ||||||
|  | 
 | ||||||
|  | -- | Read a json from a file and decode/parse it as the target type, if we can. | ||||||
|  | -- Example: | ||||||
|  | -- >>> readJsonFile "in.json" :: IO MixedAmount | ||||||
|  | readJsonFile :: FromJSON a => FilePath -> IO a | ||||||
|  | readJsonFile f = do | ||||||
|  |   bs <- BL.readFile f | ||||||
|  |   let v = fromMaybe (error "could not decode bytestring as json value") (decode bs :: Maybe Value)  | ||||||
|  |   case fromJSON v :: FromJSON a => Result a of | ||||||
|  |     Error e   -> error e | ||||||
|  |     Success t -> return t | ||||||
|  | 
 | ||||||
|  | -- | Write some to-JSON-convertible haskell value to a json file, if we can. | ||||||
|  | -- Example: | ||||||
|  | -- >>> writeJsonFile "out.json" nullmixedamt | ||||||
|  | writeJsonFile :: ToJSON a => FilePath -> a -> IO () | ||||||
|  | writeJsonFile f v = BL.writeFile f (encode $ toJSON v) | ||||||
| @ -139,6 +139,7 @@ library | |||||||
|       Hledger.Web.Handler.RegisterR |       Hledger.Web.Handler.RegisterR | ||||||
|       Hledger.Web.Handler.UploadR |       Hledger.Web.Handler.UploadR | ||||||
|       Hledger.Web.Import |       Hledger.Web.Import | ||||||
|  |       Hledger.Web.Json | ||||||
|       Hledger.Web.Main |       Hledger.Web.Main | ||||||
|       Hledger.Web.Settings |       Hledger.Web.Settings | ||||||
|       Hledger.Web.Settings.StaticFiles |       Hledger.Web.Settings.StaticFiles | ||||||
|  | |||||||
| @ -91,6 +91,7 @@ library: | |||||||
|   - Hledger.Web.Handler.RegisterR |   - Hledger.Web.Handler.RegisterR | ||||||
|   - Hledger.Web.Handler.UploadR |   - Hledger.Web.Handler.UploadR | ||||||
|   - Hledger.Web.Import |   - Hledger.Web.Import | ||||||
|  |   - Hledger.Web.Json | ||||||
|   - Hledger.Web.Main |   - Hledger.Web.Main | ||||||
|   - Hledger.Web.Settings |   - Hledger.Web.Settings | ||||||
|   - Hledger.Web.Settings.StaticFiles |   - Hledger.Web.Settings.StaticFiles | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user