问题
I have a large struct which I need to be an instance of FromJSON so that I can parse my json data into it.
I would like to derive automatically, but a single field needs "special care" in that it is an object in json and I want it to be an array of the values in my struct. How can I do this without writing a huge FromJson implementation repeating all the fields?
Example json:
{"myobject": {"one": 1, "two": 2}, ...many_more_fields...}
Example struct:
data MyStruct = MyStruct {
myobject :: [Int],
...many_more_fields,...
} deriving (Generic)
How do I do this elegantly?
回答1:
You should create a newtype
for your special field:
newtype MySpecialType = MySpecialType [Int]
instance FromJSON MySpecialType where ....
data MyStruct = MyStruct {
myobject:: MySpecialType,
...
}
Now the instance for MyStruct
becomes entirely regular and can be handed off to Template Haskell in the normal way.
回答2:
To avoid carrying the newtype from Paul Johnson's very good answer all across the codebase, you can also generalize your type as follows, making the type of myobject
a parameter:
data MyStruct_ intList = MyStruct {
myobject :: intlist,
...
} deriving (Functor, Generic)
type MyStruct = MyStruct [Int]
instance FromJSON MyStruct where
parseJSON = (fmap . fmap) (\(MySpecialType i) -> i)
. genericParseJSON defaultOptions
genericParseJSON
above gets instantiated with MyStruct MySpecialType
, and then the field gets unwrapped via fmap
(noting MyStruct_
is a Functor
)
I also just wrote a blogpost about "type surgery", applied to this kind of problem so that you can keep the original type unmodified.
The generic-data-surgery library can derive a generic type with the same Generic
structure as MyStruct_ MySpecialType
above, to be used by aeson's genericParseJSON
. The surgery modifyRField
then applies the function \(MySpecialType i) -> i
to the myobject
field, finally yielding MyStruct
.
import Generic.Data.Surgery (fromOR, toOR', modifyRField)
-- The original type
data MyStruct = MyStruct {
myobject :: [Int],
...
} deriving (Generic)
instance FromJSON MyStruct where
parseJSON = fmap (fromOR . modifyRField @"myobject" (\(MySpecialType i) -> i) . toOR')
. genericParseJSON defaultOptions
来源:https://stackoverflow.com/questions/53448958/aeson-derive-some-but-not-all-fields-of-a-struct