aeson
aeson copied to clipboard
Add some way (or document if it already exists) to extract data type name in 'defaultOptions'
I want to add full data type name to every field of my data type. Like this:
data ProjectManager = ProjectManager
{ projectManagerName :: Text
, projectManagerSalary :: Natural
}
And I want to have JSON encoding like this: { name: "Vasya Pupkin", salary: 42 }. I don't see any way to implement general Options data type which can automatically work for any type with fieldLabelModifier because I don't know type name from context.
That would be a good feature to have. I think adding the type name as an argument to fieldLabelModifier would be ideal (to allow users to adapt it to their own variations of prefix/suffix pattern), but for backwards compatibility that would probably be a new field instead.
In the meantime, a workaround for you may be to define wrappers around genericToJSON/genericParseJSON that use the type information from Generic to modify the fieldLabelModifier field.
toJSON = genericToJSON defaultOptions { fieldLabelModifier = drop (length "ProjectManager") }
I've found something like this good enough for my use cases, you could go a step further and extract the type name with generics. Maybe you also want some checks to make sure that every field does indeed have that prefix?
@bergmark
toJSON = genericToJSON defaultOptions { fieldLabelModifier = drop (length "ProjectManager") }
Well, this is not reliable enough. If I change type name I need not to forget all instances. Sure, this can be caught by roundtrip tests. But still extra few steps I would like to avoid if it's possible.
Your example can be type safe (the only cost is boilerplate) if some type family for extracting Symbol from type name existed, like this:
toJSON = genericToJSON defaultOptions
{ fieldLabelModifier = drop $ length $ symbolVal $ Proxy @(TypeName ProjectManager)
}
This can be implemented shorter with simple utility function:
toJSON = genericToJSON defaultOptions { fieldLabelModifier = dropType @ProjectManager }
But looks like such type family doesn't exist:
- https://stackoverflow.com/questions/48709079/how-to-convert-any-type-to-symbol-using-type-families
Another workaround: If you are prefixing the fields because of name clashes you can use DuplicateRecordFields.
I'm all for adding something like dropType by the way, but with generics I think it should be possible to write it in such a way where you don't have to write the type name, and where you can't write another type name by accident. Maybe you should also be able to choose whether it's the type name or the constructor name you want to strip?
Here's a function typeName that gives a String of the first data constructor in a generic type. It can also be extracted as a Symbol at the typelevel first. (I just wrote an answer to your SO question with all that.)
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeApplications #-}
import GHC.Generics
typeName :: forall a d f p. (Generic a, Rep a ~ D1 d f, Datatype d) => String
typeName = datatypeName (from @a undefined)
main = print (typeName @(Maybe Int)) -- "Maybe"
After this GHC proposal is accepted and implemented, it becomes possible to write typeName function without Generic:
- https://github.com/ghc-proposals/ghc-proposals/pull/164