You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell中基于Aeson实现上下文感知的JSON脱敏序列化

推荐方案:自定义带上下文的JSON序列化类型类

首先,你找对方向了——原生Aeson的toJSON是纯函数,没办法携带当前用户这类上下文信息,所以必须扩展它的能力。我最推荐你用自定义ToJSONReader类型类的方案,这是最优雅、可维护性最高的实现方式,下面给你细化具体的代码实现:

1. 定义核心类型类

先创建一个支持Reader上下文的序列化类型类,同时给所有已有ToJSON实例的类型提供默认实现,这样大部分类型不用额外修改就能复用原有序列化逻辑:

import Control.Monad.Reader
import Data.Aeson (ToJSON(..), Value, object, (.=))
import Data.ByteString.Lazy (ByteString)

-- 假设你的用户ID类型是这样的
newtype UserId = UserId Int deriving (Eq, Show, ToJSON)

-- 定义带Reader上下文的JSON序列化类型类
class ToJSONReader a where
  toJSONReader :: a -> Reader UserId Value

-- 给所有已有ToJSON实例的类型提供默认实现
instance ToJSON a => ToJSONReader a where
  toJSONReader = return . toJSON

2. 为需要脱敏的类型实现实例

针对你的嵌套类型MyNestedType,重写toJSONReader方法,根据当前用户判断是否脱敏:

data MyNestedType = MyNestedType
  { owner :: UserId
  , sensitiveData :: String
  , otherData :: Int
  , nestedChild :: AnotherType -- 假设有嵌套子类型
  } deriving (Show)

-- 默认的纯函数序列化(供所有者查看时使用)
instance ToJSON MyNestedType where
  toJSON x = object
    [ "owner" .= owner x
    , "sensitiveData" .= sensitiveData x
    , "otherData" .= otherData x
    , "nestedChild" .= nestedChild x
    ]

-- 带上下文的脱敏序列化
instance ToJSONReader MyNestedType where
  toJSONReader x = do
    currentUser <- ask
    if owner x == currentUser
      then return $ toJSON x -- 所有者查看完整数据
      else return $ object
        [ "owner" .= owner x
        , "sensitiveData" .= String "REDACTED" -- 脱敏敏感字段
        , "otherData" .= otherData x -- 非敏感字段保留
        , "nestedChild" .= runReader toJSONReader (nestedChild x) -- 递归处理嵌套类型
        ]

这里的优势是递归处理嵌套结构:如果你的类型里包含其他需要脱敏的子类型,只要给子类型也实现ToJSONReader,就会自动应用对应的脱敏逻辑,不用手动遍历整个结构。

3. 顶层编码函数

最后写一个方便使用的顶层函数,传入用户ID和数据,直接输出脱敏后的JSON:

encodeWithUser :: ToJSONReader a => UserId -> a -> ByteString
encodeWithUser user = encode . runReader toJSONReader user

对比其他方案的优缺点

  • 先转Value再脱敏:确实可行,但嵌套结构深的时候需要递归遍历Value(比如Object、Array),代码繁琐且容易遗漏字段,维护成本高。
  • 扩展类型加脱敏标记:会污染原有的数据类型,每次修改原类型都要同步修改带标记的版本,不够简洁。
  • 创建RedactedMyType:本质是复制原类型结构,重复代码过多,原类型结构变化时需要同步修改脱敏版本,后期维护很麻烦。

额外优化:更通用的Monad上下文

如果以后需要更多上下文(比如请求ID、权限等级),可以把类型类改成支持任意MonadReader的版本,扩展性更强:

class MonadReader r m => ToJSONM r m a where
  toJSONM :: a -> m Value

instance (ToJSON a, MonadReader r m) => ToJSONM r m a where
  toJSONM = return . toJSON

这样你可以把Reader UserId换成Reader AppContext(包含更多上下文信息),不用修改核心的序列化逻辑。

内容的提问来源于stack exchange,提问作者Xavier Shay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:40:51