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:
- Define instances of
ToJSONfor such data-types in my application - Write my own type-class, say
ToJsonLogs, and make it reuseToJSONinstances 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.
You are very close to a possible extension-free solution. The thing you should consider is to create a wrapper for the original
ToJsonclass members:If you want to restrict the wrapper only for
ToJsoninstances, you will need to enable few extensions:If you don't enjoy this wrapper, you may hide it under another definition of
toJsonLogs: