更好的方法来收集aeson的解析器中对象的所有未使用字段? [英] Better ways to collect all unused field of an Object in aeson's Parser?

查看:72
本文介绍了更好的方法来收集aeson的解析器中对象的所有未使用字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我要为数据类型实现 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 Applicatives, 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆