How to conditionally parse JSON based on settings in a Reader environment?

Is there a way to pass the reading environment into the JSON (de) Aeson serialization functions? Here is a real example of why this might be required?

-- JSON instances for decimal -- ORPHAN instances

defaultPrecision :: Word8
defaultPrecision = fromInteger 2

instance ToJSON Data.Decimal.Decimal  where
  toJSON d = toJSON $ show d

instance FromJSON Data.Decimal.Decimal  where
  -- TODO: New problem! How do we get the precision dynamically, based on
  -- the currency settings of the logged-in user
  parseJSON (Number n) = return $ Data.Decimal.realFracToDecimal defaultPrecision n
  parseJSON x = fail $ "Expectig a number in the JSON to parse to a Decimal. Received " ++ (show x)
+4
source share
1 answer

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 . :

{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE UndecidableInstances #-}

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)
+4

Source: https://habr.com/ru/post/1679122/


All Articles