更好的方法来收集aeson的解析器中对象的所有未使用字段? [英] Better ways to collect all unused field of an Object in aeson's Parser?
问题描述
假设我要为数据类型实现 FromJSON
.以下是完整的源代码:
Suppose I want to implement FromJSON
for a data type. Below are the complete source code:
{-# LANGUAGE
NamedFieldPuns
, OverloadedStrings
, TupleSections
, ViewPatterns
#-}
module Main
( main
) where
import Data.Aeson
import Control.Monad
import qualified Data.HashMap.Strict as HM
import qualified Data.Map.Strict as M
import qualified Data.Text as T
data Foo
= Foo
{ aaa :: Int
, bbb :: T.Text
, ccc :: Maybe (Int, Int)
, extra :: M.Map T.Text T.Text
}
instance FromJSON Foo where
parseJSON = withObject "Foo" $ \obj -> do
aaa <- obj .: "aaa"
bbb <- obj .: "bbb"
ccc <- obj .:? "ccc"
let existingFields = T.words "aaa bbb ccc"
obj' =
-- for sake of simplicity, I'm not using the most efficient approach.
filter ((`notElem` existingFields) . fst)
. HM.toList
$ obj
(M.fromList -> extra) <- forM obj' $ \(k,v) ->
withText "ExtraText" (pure . (k,)) v
pure Foo {aaa,bbb,ccc,extra}
main :: IO ()
main = pure ()
此数据类型 Foo
有一堆可能不同类型的字段,最后还有 extra
来收集所有剩余字段.
This data type Foo
has a bunch of fields of potentially different types and in the end there is extra
to collect all remaining fields.
显然,没有人会喜欢每次添加/删除/更新某些字段时更新 existingFields
,是否有建议的方法来收集未使用的字段?
Obviously no one would enjoy updating existingFields
every time some fields get add/remove/update-ed, any recommended approach on collecting unused fields?
我可以想到的另一种方法是将 StateT
堆叠在初始状态,并以 obj
(转换为 Map
)作为初始状态,并使用 Data.Map.splitLookup
之类的工具释放"使用过的字段.但是我不愿意这样做,因为它涉及到围绕monad堆栈的提升,并且听起来并不十分好,因为与从 Map
进行过滤相比,每次从 Map
中删除一个元素的效果并不理想.最后一遍> HashMap .
An alternative that I can think of is to stack a StateT
on top with obj
(converted to Map
) as the initial state, and use something like Data.Map.splitLookup
to "discharge" used fields. But I'm reluctant to do so as it will involve some lifting around monad stacks and it doesn't sound very good performance-wise removing elements one at a time from Map
in comparison to filtering through HashMap
in one pass in the end.
推荐答案
没有人会喜欢每次更新某些字段时都会更新现有的字段添加/删除/更新
no one would enjoy updating existingFields every time some fields get add/remove/update-ed
考虑此功能
import Data.Aeson.Types (Parser)
import Data.Text (Text)
import Control.Monad.Trans.Writer
import Data.Functor.Compose
keepName :: (Object -> Text -> Parser x)
-> Object -> Text -> Compose (Writer [Text]) Parser x
keepName f obj fieldName = Compose $ do
tell [fieldName]
pure (f obj fieldName)
它以 .:
或 <代码>解析器 嵌套在 Compose
新类型,它会自动为我们提供 Applicative
实例,因为如文档所述:
It takes as input an operator like .:
or .:?
and "enriches" its result value so that, instead of returning a Parser
, it returns a Parser
nested inside a Writer
that serves to accumulate the supplied field names. The composition is wrapped in the Compose
newtype, which automatically gives us an Applicative
instance because, as mentioned in the docs:
(Applicative f,Applicative g)=> Applicative(Compose f g)
(Applicative f, Applicative g) => Applicative (Compose f g)
(尽管合成不是 Monad
.另外请注意,我们使用的是 Writer
,而不是 WriterT
.我们是嵌套 Applicative
s ,不使用monad变压器.
(The composition is not a Monad
though. Also take note that we are using Writer
and not WriterT
. We are nesting Applicative
s, not applying monad transformers).
其余代码变化不大:
{-# LANGUAGE ApplicativeDo #-}
instance FromJSON Foo where
parseJSON = withObject "Foo" $ \obj -> do
let Compose (runWriter -> (parser,existingFields)) =
do aaa <- keepName (.:) obj "aaa"
bbb <- keepName (.:) obj "bbb"
ccc <- keepName (.:?) obj "ccc"
pure Foo {aaa,bbb,ccc,extra = mempty}
obj' =
filter ((`notElem` existingFields) . fst)
. HM.toList
$ obj
(M.fromList -> extra) <- forM obj' $ \(k,v) ->
withText "ExtraText" (pure . (k,)) v
r <- parser
pure $ r { extra }
这篇关于更好的方法来收集aeson的解析器中对象的所有未使用字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!