如何处理不完整的JSON / Record类型(IE缺少我以后填写的必填字段)?

时间:2017-02-03 20:14:32

标签: haskell yesod aeson

编辑:对于那些患有类似疾病的人,我发现这与“可扩展记录问题"”有关,我将亲自研究这些问题。

EDIT2:我已经开始通过非常明确地了解数据类型并且每个语义数据单元具有多种数据类型来解决这个问题(几周后)。例如,如果数据库包含X,则我的代码有一个XAction用于表示我要对X执行的操作,而XResponse用于表示X转到http客户端。然后我需要构建支持代码来在实例之间穿梭。不理想,但是,我喜欢它的明确,并且希望当我的模型结晶时,它不应该真的需要很多保持,并且应该非常可靠。

我不确定解决这个问题的正确抽象级别是什么(即记录?还是Yesod?)所以我只是简单地说明了这个问题。

简单案例/ TL; DR

我想将请求主体解码为类型

data Comment = Comment {userid :: ..., comment :: ...}

但实际上我并不希望请求正文包含userid,服务器将根据其Auth Headers(或者我想将数据设置为默认填充字段的任何地方)提供。{ / p>

所以他们实际上传给我的是:

data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)

然后我把它变成Comment。但同时保持几乎相同的类型是麻烦,而不是干。

如何解决这个问题?

问题详情

我有一个记录类型:

data Comment = Comment {userid :: ..., comment :: ...}

我有一条POST路线:

postCommentR :: Handler Value
postCommentR = do
  c <- requireJsonBody :: (Handler Comment)
  insertedComment <- runDB ...
  returnJson insertedComment

请注意,路由要求用户提供useridComment类型,这至少是冗余的,因为它们的id与其auth标头相关联。最坏的情况是,这意味着我需要检查用户是否正在添加自己的ID,或丢弃他们提供的ID,在这种情况下,为什么他们在第一种情况下提供它。

所以,我想要一个Comment减去userid的记录类型,但我不知道如何智能地做到这一点。

My Current(糟糕但有效)解决方案

所以我创建了一个带有派生FromJSON的自定义类型(对于请求体),它几乎完全是Comment类型的冗余。

data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)

然后我的新路由需要根据此解码请求正文,然后将SimpleCommentuserid字段合并以使其成为Comment

postComment2R :: Handler Value
postComment2R = do
  c <- requireJsonBody :: (Handler SimpleComment)
  (uid, _) requireAuthPair
  insertedComment <- runDB $ insertEntity (Comment { commentUserid  = uid
                                                   , commentComment = comment c})
  returnJson ...

谈论样板。我的用例比这个简单的Comment类型更复杂。

如果它有所影响,您可以告诉我,我正在使用 Yesod Scaffolding

3 个答案:

答案 0 :(得分:3)

我通常做的是为了获得一个类型减去一个字段只是为了有一个函数,它接受该字段并返回类型。在您的情况下,您只需要为UserId -> Comment声明一个JSON实例。好吧,这似乎并不自然,你必须手动去,但实际上效果很好,特别是因为评论中只有一个UserId类型的字段。

答案 1 :(得分:2)

我喜欢的解决方案是使用包装来处理来自/转到DB的东西:

data Authenticated a = Authenticated
  { uid :: Uid
  , thing :: a
  } deriving (Show)

然后,您可以让Comment成为SimpleComment,并在知道用户ID后将其变为Authenticated Comment

答案 2 :(得分:0)

我也在寻找一种解决这个问题的好方法。 : - )

我通常在代码中执行的操作是直接在Aeson的类型Value上操作。这是我当前项目中的一些示例代码:

import qualified Data.HashMap.Strict as HM

removeKey :: Text -> Value -> Value
removeKey key (Object xs) = Object $ HM.delete key xs
removeKey _ ys = ys

我直接操作值Object并删除javascript对象中的特定键。

在Yesod处理程序代码中,我执行此处理:

myHandler :: Handler RepJson
myHandler = do
    userId <- insert $ User "sibi" 23
    guser <- getJuser user
    let guser' =  removeKey "someId" $ toJSON guser
    return $ repJson $ object [ "details" .= guser' ]

在某些情况下,我实际上想要为传出的JSON对象添加一些特定的键。对于那些,我定义了特定的辅助函数,它们在类型Value上运行。虽然这并不完美,但它一直在帮助我避免大量的样板代码。