@peterbecich
On the Aeson side of things in Haskell you actually have quite a bit of control over the tagging. I see the Stackoverflow post you linked refers to the defaultOptions
for generic encoding and on the Data.Aeson
Hackage page you will see the set of options that you can customize. A few of them are around tagging.
instance ToJSON Person where
toEncoding = genericToEncoding defaultOptions -- customize these options
instance FromJSON Person
Thereās also a funky package called deriving-aeson
to do this when you define your data type.
Let me preface this by saying I am a complete newbie with type-level Haskell (aside from Servant), but the āpatternā was simple enough for me to follow along and adopt with my own types. All of those same features are available at the value level too by customizing the defaultOptions
for the ToJSON
instance as I mentioned at the start.
e.g. using your actual Foo
data type:
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE PolyKinds #-}
import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Deriving.Aeson as DA
import Deriving.Aeson (CustomJSON(..))
data Foo = Foo
{ _buttonOnCount :: Int
, _buttonClickCount :: Int
} deriving ( Show, Eq, Generic )
deriving ( FromJSON, ToJSON )
via CustomJSON '[DA.TagSingleConstructors, DA.SumObjectWithSingleField] Foo
ā¦which gives output of:
Ī»> BL.putStrLn $ A.encode [ Foo 1 2, Foo 3 4 ]
[{"Foo":{"_buttonOnCount":1,"_buttonClickCount":2}},{"Foo":{"_buttonOnCount":3,"_buttonClickCount":4}}]
Itās a bit more lightweight as you can see than having "tag"
and "contents"
(or equivalents) in your JSON.
If I was going to re-use that pattern on a lot of types I could do:
type MyJSONPattern =
CustomJSON '[DA.TagSingleConstructors, DA.SumObjectWithSingleField]
data Foo = Foo
{ _buttonOnCount :: Int
, _buttonClickCount :: Int
} deriving ( Show, Eq, Generic )
deriving ( FromJSON, ToJSON )
via MyJSONPattern Foo
If I was working with a more complicated constructor type and wanted snake_case
for both the tag and the keys if it has multiple words, I would do:
-- | A data type where I want things in snake_case and _button stripped
data ComplexFoo = ComplexFoo
{ _buttonOnCount :: Int
, _buttonClickCount :: Int
} deriving ( Show, Eq, Generic )
deriving ( FromJSON, ToJSON )
via (MyJSONPattern2 "_button") ComplexFoo
-- | The type I'm deriving via that makes my JSON output
-- | ..that I can reuse for any product type I want to convert to JSON in a similar way
type MyJSONPattern2 stripme = CustomJSON
'[ DA.TagSingleConstructors
, DA.SumObjectWithSingleField
, DA.ConstructorTagModifier DA.CamelToSnake
, DA.FieldLabelModifier (DA.StripPrefix stripme, DA.CamelToSnake)
]
Giving output like:
Ī»> BL.putStrLn $ A.encode [ ComplexFoo 1 2, ComplexFoo 3 4 ]
[{"complex_foo":{"on_count":1,"click_count":2}},{"complex_foo":{"on_count":3,"click_count":4}}]
ā¦or prettyprinted a bit:
[
{
"complex_foo": {
"click_count": 2,
"on_count": 1
}
},
{
"complex_foo": {
"click_count": 4,
"on_count": 3
}
}
]
Again, you can see Aeson pretty flexible, and you donāt have to spell it out with "tag"
and "contents"
. Nor do you have to have your JSON include the _button
parts from what I was assume you making lenses in Haskell