json之使用 Servant 和 Persistent 表示 JSON 中的外键关系

daizhj 阅读:64 2025-06-02 22:19:02 评论:0

今天早上我跟着this interesting tutorial关于使用 Servant 构建一个简单的 API 服务器。

在教程的最后,作者建议添加一个博客类型,所以我想我会试一试,但我在尝试实现和序列化扩展到教程中的逻辑的外键关系时遇到了困难 (也许这里有一个重要的披露:我对 Servant 和 Persistent 都是新手)。

这是我的 Persistent 定义(我添加了 Post):

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| 
User 
    name String 
    email String 
    deriving Show 
Post 
    title String 
    user UserId 
    summary String 
    content String 
    deriving Show 
|] 

本教程为 Servant API 构建了一个单独的 Person 数据类型,因此我也添加了一个名为 Article 的数据类型:

-- API Data Types 
data Person = Person 
    { name :: String 
    , email :: String 
    } deriving (Eq, Show, Generic) 
 
data Article = Article 
    { title :: String 
    , author :: Person 
    , summary :: String 
    , content :: String 
    } deriving (Eq, Show, Generic) 
 
instance ToJSON Person 
instance FromJSON Person 
 
instance ToJSON Article 
instance FromJSON Article 
 
userToPerson :: User -> Person 
userToPerson User{..} = Person { name = userName, email = userEmail } 

但是,现在,当我尝试创建一个将 Post 转换为 Article 的函数时,我在尝试处理 User外键:

postToArticle :: Post -> Article 
postToArticle Post{..} = Article { 
  title = postTitle 
  , author = userToPerson postUser -- this fails 
  , summary = postSummary 
  , content = postContent 
  } 

我尝试了很多东西,但上面的似乎接近我想进入的方向。它没有编译,但是,由于以下错误:

Couldn't match expected type ‘User’ 
            with actual type ‘persistent-2.2.2:Database.Persist.Class.PersistEntity.Key 
                                User’ 
In the first argument of ‘userToPerson’, namely ‘postUser’ 
In the ‘author’ field of a record 

最终,我不太确定 PersistEntity.Key User 到底是什么,而且我错误的谷歌搜索并没有让我更接近。

如何处理这种外键关系?


工作版本

感谢 haoformayor 编辑了答案

postToArticle :: MonadIO m => Post -> SqlPersistT m (Maybe Article) 
postToArticle Post{..} = do 
  authorMaybe <- selectFirst [UserId ==. postUser] [] 
  return $ case authorMaybe of 
    Just (Entity _ author) -> 
      Just Article { 
          title = postTitle 
        , author = userToPerson author 
        , summary = postSummary 
        , content = postContent 
        } 
    Nothing -> 
      Nothing 

请您参考如下方法:

对于某些记录类型rEntity r是包含 Key rr 的数据类型。您可以将其视为一个拼凑而成的元组 (Key r, r)

(您可能想知道什么是 Key r。不同的后端有不同类型的 Key r。对于 Postgres,它将是一个 64 位整数。对于 MongoDB,有是对象 ID。documentation goes into more detail。它是允许 Persistent 支持多个数据存储的抽象。)

这里的问题是您有一个关键用户。我们的策略是为您提供一个 Entity User,我们可以从中提取一个 User。幸运的是,使用 selectFirstKey UserEntity User 很容易——访问数据库。从 Entity UserUser 是一种模式匹配。

postToArticle :: MonadIO m => Post -> SqlPersistT m (Maybe Article) 
postToArticle Post{..} = do 
  authorMaybe <- selectFirst [UserId ==. postUser] [] 
  return $ case authorMaybe of 
    Just (Entity _ author) ->   
      Article { 
          title = postTitle 
        , author = author 
        , summary = postSummary 
        , content = postContent 
        } 
    Nothing -> 
      Nothing 

恶心,更通用的版本

我们假设上面有一个 SQL 后端,但该函数也有更通用的类型

postToArticle :: 
  (MonadIO m, PersistEntity val, backend ~ PersistEntityBackend val) => 
  Post -> ReaderT backend m (Maybe Article) 

如果您不使用 SQL 后端,您可能需要它。


标签:json
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号