If an instance depends on some runtime value, then what you really want is the ability to instantiate at runtime. You can implement FromJSONfor Readerhow this is done in gist . But, as you rightly pointed out, you cannot do the same for ToJSON, because you do not know this accuracy. The simplest solution is to simply store precision as a separate field in the data type. Like this:
data DecimalWithPrecision = MkDWP
{ value :: Decimal
, precision :: Word8
}
, .
, , ( , , ), . , " - ", ToJSON/FromJSON JsonDict Money_ :
newtype Money_ = Money_ (Reader Word8 Decimal)
data JsonDict a = JsonDict
{ jdToJSON :: a -> Value
, jdParseJSON :: Value -> Parser a
}
mkJsonDict :: Word8 -- precision
-> JsonDict Money_
( ) , Word8 , . . .
ToJSON , reflection. Precision - , . , , , . . , , Arbitrary . :
{-
{-
import Control.Monad.Reader (Reader, ask)
import Data.Aeson (FromJSON (..), Result (..), ToJSON (..),
Value, fromJSON, withNumber)
import Data.Aeson.Types (Parser)
import Data.Decimal (Decimal, realFracToDecimal)
import Data.Proxy (Proxy (..))
import Data.Reflection (Reifies (reflect), reify)
import Data.Word8 (Word8)
newtype PreciseDecimal s = PD Decimal
instance Reifies s Int => FromJSON (PreciseDecimal s) where
parseJSON = withNumber "a number" $ \n -> do
let precision = fromIntegral $ reflect (Proxy :: Proxy s)
pure $ PD $ realFracToDecimal precision n
instance Reifies s Int => ToJSON (PreciseDecimal s) where
toJSON (PD decimal) =
let precision = reflect (Proxy :: Proxy s)
ratDec = realToFrac decimal :: Double
in toJSON ratDec -- use precision if needed
makeMoney :: Decimal -> Reader Word8 (Value, Decimal)
makeMoney value = do
precision <- fromIntegral <$> ask
let jsoned = reify precision $ \(Proxy :: Proxy s) ->
toJSON (PD value :: PreciseDecimal s)
let parsed = reify precision $ \(Proxy :: Proxy s) ->
let Success (PD res :: PreciseDecimal s)
= fromJSON jsoned in res
pure (jsoned, parsed)
, , :
ghci> runReader (makeMoney 3.12345) 2
(Number 3.12345,3.12)