更好的方法来收集aeson解析器中对象的所有未使用字段?

问题描述 投票:2回答:1

假设我想为数据类型实现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进行过滤结束。

haskell aeson
1个回答
1
投票

没有人会喜欢每次获取某些字段时都会更新现有的字段 添加/删除/更新

考虑此功能

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    
© www.soinside.com 2019 - 2024. All rights reserved.