Reputation: 6793
I want to output my application's logs in JSON, but there are some ubiquitous data-types for which ToJSON
instances are not defined - most notably SomeException
and the entire Exception
hierarchy of types.
I have two choices:
ToJSON
for such data-types in my applicationToJsonLogs
, and make it reuse ToJSON
instances as much as possible.The first is the path of "least resistance" but it has other implications. Since type-class instances are global in nature, I might end-up defining ToJSON
instances that break something. Also, for the same data-structure, I might want the JSON in APIs to be different from the JSON in logs (for example, scrubbing keys, auth-tokens, and other sensitive data OR truncating very long text fields).
This questions is about exploring the second option. How do I go about doing something like the following:
class ToJsonLogs a where
toJsonLogs :: a -> Aeson.Value
default toJsonLogs :: (ToJSON a) => a -> Aeson.Value
toJsonLogs = toJSON
instance ToJsonLogs SomeException where
toJsonLogs = toJSON . displayException
I tried the above idea, but it failed at the very first step itself. Here's an example data-structure:
data SyncResult = SyncResult
{ resAborted :: !Bool
, resSuccessful :: !Int
, resFailed :: ![(Int, SomeException)]
} deriving (Show)
I can't derive ToJsonLogs
without first deriving ToJSON
for the entire data-structure. Derivation of ToJSON
fails because of SomeException
. Hence the title of this question.
I even tried fooling around with Generics, but as usual, got stuck again.
Upvotes: 2
Views: 89
Reputation: 7159
You are very close to a possible extension-free solution. The thing you should consider is to create a wrapper for the original ToJson
class members:
class ToJsonLogs a where
toJsonLogs :: a -> Aeson.Value
newtype WrapToJson a = WrapToJson a -- actually an Identity
instance ToJson a => ToJsonLogs (WrapToJson a) where
toJsonLogs (WrapToJson x) = toJson x
-- example
logInt :: Int -> Aeson.value
logInt x = toJsonLogs (WrapJson x)
If you want to restrict the wrapper only for ToJson
instances, you will need to enable few extensions:
{-# LANGUAGE GADTSyntax, ExistentialQuantifiaction #-}
data WrapToJson a where WrapToJson :: ToJson a => a -> WrapToJson a
If you don't enjoy this wrapper, you may hide it under another definition of toJsonLogs
:
toJsonLogs' :: ToJson a => a -> Aeson.value
toJsonLogs' = toJsonLogs . WrapToJson
Upvotes: 0