如何处理不完整的 JSON/Record 类型(IE 缺少我稍后会填写的必填字段)?
How to deal with incomplete JSON/Record types (IE missing required fields which I'll later fill in)?
编辑:对于那些有类似疾病的人,我发现这与 "Extensible Records Problem" 有关,我将亲自研究更多。
EDIT2:我已经开始解决这个问题(几周后),方法是非常明确地说明数据类型,并且每个语义数据单元具有多种数据类型。例如,如果数据库包含一个 X
,我的代码有一个 XAction
用于表示我想用 X
做的事情,而 XResponse
用于中继 X
s 到 http 客户端。然后我需要构建用于在实例之间穿梭的支持代码。不理想,但是,我喜欢它的明确性,希望当我的模型具体化时,它真的不需要太多维护,而且应该非常可靠。
我不确定解决这个问题的正确抽象级别是什么(即记录?还是 Yesod?)所以我只列出简单的案例。
简单案例/TL;DR
我想将请求 body 解码为类型
data Comment = Comment {userid :: ..., comment :: ...}
但实际上我不希望请求 body 包含 userid
,服务器将根据他们的 Auth Headers 提供它,(或者我想获取数据的任何地方默认填写字段)。
所以他们实际上传递给我的是:
data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)
我把它变成 Comment
。但是同时维护这两种 nearly-identical 类型很麻烦,而不是 DRY。
如何解决这个问题?
问题详情
我有一个记录类型:
data Comment = Comment {userid :: ..., comment :: ...}
我有一条POST路线:
postCommentR :: Handler Value
postCommentR = do
c <- requireJsonBody :: (Handler Comment)
insertedComment <- runDB ...
returnJson insertedComment
请注意,路由要求用户提供他们的 userid
(在 Comment
类型中,这至少是多余的,因为他们的 ID 与他们的身份验证相关联 headers。在最糟糕的是,这意味着我需要检查用户是在添加他们自己的 ID,还是丢弃了他们提供的 ID,在这种情况下,他们为什么要在第一种情况下提供它。
所以,我想要 Comment
减去 userid
的记录类型,但我不知道如何巧妙地做到这一点。
我当前(糟糕但有效)的解决方案
所以我用派生的 FromJSON
(针对请求 body)创建了一个自定义类型,它几乎与 Comment
类型完全冗余。
data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)
然后我的新路由需要根据这个解码请求body,然后将一个SimpleComment
和一个userid
字段合并为一个Comment
:
postComment2R :: Handler Value
postComment2R = do
c <- requireJsonBody :: (Handler SimpleComment)
(uid, _) requireAuthPair
insertedComment <- runDB $ insertEntity (Comment { commentUserid = uid
, commentComment = comment c})
returnJson ...
谈论样板。而且我的用例比这个简单的 Comment
类型更复杂。
如果考虑到这一点,您可能会说,我正在使用 Yesod 脚手架。
我也在寻找解决这个问题的好方法。 :-)
我在代码中通常做的是直接对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
类型上运行。虽然这并不完美,但它一直在帮助我避免大量样板代码。
为了获得一个类型减去一个字段,我通常做的只是拥有一个接受该字段和 return 类型的函数。在您的情况下,您只需要为 UserId -> Comment
声明一个 JSON 实例。好吧,这看起来不太自然,您必须手动进行,但实际上效果很好,尤其是当 Comment 中只有一个 UserId 类型的字段时。
我喜欢的一个解决方案是对 from/go 进入数据库的事物使用包装器:
data Authenticated a = Authenticated
{ uid :: Uid
, thing :: a
} deriving (Show)
然后你可以让 Comment
只是 SimpleComment
并在你知道用户 ID 后将其变成 Authenticated Comment
。
编辑:对于那些有类似疾病的人,我发现这与 "Extensible Records Problem" 有关,我将亲自研究更多。
EDIT2:我已经开始解决这个问题(几周后),方法是非常明确地说明数据类型,并且每个语义数据单元具有多种数据类型。例如,如果数据库包含一个 X
,我的代码有一个 XAction
用于表示我想用 X
做的事情,而 XResponse
用于中继 X
s 到 http 客户端。然后我需要构建用于在实例之间穿梭的支持代码。不理想,但是,我喜欢它的明确性,希望当我的模型具体化时,它真的不需要太多维护,而且应该非常可靠。
我不确定解决这个问题的正确抽象级别是什么(即记录?还是 Yesod?)所以我只列出简单的案例。
简单案例/TL;DR
我想将请求 body 解码为类型
data Comment = Comment {userid :: ..., comment :: ...}
但实际上我不希望请求 body 包含 userid
,服务器将根据他们的 Auth Headers 提供它,(或者我想获取数据的任何地方默认填写字段)。
所以他们实际上传递给我的是:
data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)
我把它变成 Comment
。但是同时维护这两种 nearly-identical 类型很麻烦,而不是 DRY。
如何解决这个问题?
问题详情
我有一个记录类型:
data Comment = Comment {userid :: ..., comment :: ...}
我有一条POST路线:
postCommentR :: Handler Value
postCommentR = do
c <- requireJsonBody :: (Handler Comment)
insertedComment <- runDB ...
returnJson insertedComment
请注意,路由要求用户提供他们的 userid
(在 Comment
类型中,这至少是多余的,因为他们的 ID 与他们的身份验证相关联 headers。在最糟糕的是,这意味着我需要检查用户是在添加他们自己的 ID,还是丢弃了他们提供的 ID,在这种情况下,他们为什么要在第一种情况下提供它。
所以,我想要 Comment
减去 userid
的记录类型,但我不知道如何巧妙地做到这一点。
我当前(糟糕但有效)的解决方案
所以我用派生的 FromJSON
(针对请求 body)创建了一个自定义类型,它几乎与 Comment
类型完全冗余。
data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)
然后我的新路由需要根据这个解码请求body,然后将一个SimpleComment
和一个userid
字段合并为一个Comment
:
postComment2R :: Handler Value
postComment2R = do
c <- requireJsonBody :: (Handler SimpleComment)
(uid, _) requireAuthPair
insertedComment <- runDB $ insertEntity (Comment { commentUserid = uid
, commentComment = comment c})
returnJson ...
谈论样板。而且我的用例比这个简单的 Comment
类型更复杂。
如果考虑到这一点,您可能会说,我正在使用 Yesod 脚手架。
我也在寻找解决这个问题的好方法。 :-)
我在代码中通常做的是直接对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
类型上运行。虽然这并不完美,但它一直在帮助我避免大量样板代码。
为了获得一个类型减去一个字段,我通常做的只是拥有一个接受该字段和 return 类型的函数。在您的情况下,您只需要为 UserId -> Comment
声明一个 JSON 实例。好吧,这看起来不太自然,您必须手动进行,但实际上效果很好,尤其是当 Comment 中只有一个 UserId 类型的字段时。
我喜欢的一个解决方案是对 from/go 进入数据库的事物使用包装器:
data Authenticated a = Authenticated
{ uid :: Uid
, thing :: a
} deriving (Show)
然后你可以让 Comment
只是 SimpleComment
并在你知道用户 ID 后将其变成 Authenticated Comment
。