如何使用Aeson的genericToJSON将产品类型编码为对象而非列表?
Got it, let's tackle this encoding issue with Aeson's genericToJSON. The problem right now is that your Foo type is getting encoded with an array of two objects under the FooBar key, but you want those two objects merged into a single one. Let's break down why this is happening and how to fix it.
Why You're Getting the Array Output
Your Bar and Baz newtypes are being encoded as standalone objects (I'm guessing your options has unwrapUnaryRecords = False, which prevents Aeson from unwrapping the newtype to a raw Integer). By default, Aeson's generic encoding for a single-constructor product type (like Foo) puts all constructor parameters into an array. Combine those two behaviors, and you end up with an array of separate objects instead of one merged object.
Solution 1: Use a Record Type (Recommended)
The cleanest approach is to adjust your Foo type to use named record fields. This plays nicely with Aeson's generic encoder and makes your code more readable too.
First, update the Foo definition:
data Foo = FooBar { bar :: Bar, baz :: Baz } deriving Generic
Next, tweak your options to ensure the JSON field names match your desired Bar and Baz (capitalized):
import Data.Char (toUpper) -- Helper to capitalize the first letter of field names capitalize :: String -> String capitalize [] = [] capitalize (c:cs) = toUpper c : cs options :: Options options = defaultOptions { unwrapUnaryRecords = False -- Keeps Bar/Baz as objects instead of raw Integers , fieldLabelModifier = capitalize -- Turns lowercase "bar" into "Bar", "baz" into "Baz" }
With this setup, genericToJSON options will encode your Foo exactly how you want:
{"FooBar":{"Bar":4,"Baz":2}}
Solution 2: Manual Encoding (No Type Changes)
If you can't or don't want to modify the Foo type definition, you can manually merge the JSON objects from Bar and Baz in the ToJSON instance:
instance ToJSON Foo where toJSON (FooBar bar baz) = object [ "FooBar" .= mergeObjects (toJSON bar) (toJSON baz) ] where -- Merge two JSON Objects (safe since we know Bar/Baz encode to Objects) mergeObjects (Object o1) (Object o2) = Object (o1 <> o2) mergeObjects _ _ = Object mempty -- Fallback for unexpected cases
This uses Aeson's Object monoid instance to combine the key-value pairs from Bar and Baz into a single object, eliminating the array entirely.
内容的提问来源于stack exchange,提问作者Matthew Piziak

