假设我想为数据类型实现FromJSON
。以下是完整的源代码:
{-# 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
来收集所有剩余字段。
很显然,没有人会喜欢每次添加/删除/更新某些字段时都会更新existingFields
,是否有任何建议的方法来收集未使用的字段?
[我可以想到的另一种方法是,将StateT
(转换为obj
)作为初始状态,将Map
堆叠在顶部,然后使用Data.Map.splitLookup
之类的东西“释放”使用过的字段。但是我不愿意这样做,因为它涉及到围绕monad堆栈的提升,并且听起来并不十分好,从性能上来讲,一次从Map
中删除一个元素,而不是一次通过HashMap
进行过滤结束。
没有人会喜欢每次获取某些字段时都会更新现有的字段 添加/删除/更新
考虑此功能
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)
[它以.:
或.:
之类的运算符作为输入,并“丰富”其结果值,因此,它返回嵌套在.:?
内的.:?
而不是返回Parser
,该嵌套在Parser
内提供的字段名称。该组合物包装在Parser
新类型中,该新类型自动为我们提供了一个Parser
实例,因为如docs中所述:
((适用f,适用g)=>适用(合成f g)
(尽管合成不是
Writer
。也请注意,我们使用的是Writer
,而不是Compose
。我们是Compose
,未应用monad转换器。
其余代码变化不大:
Applicative