How to work with incomplete JSON / Record types (IE skips the necessary fields, which I will fill out later)?

EDIT: For those with similar illnesses, I have found that this is due to the “Extensible Entries” problem, which I will personally research more.


EDIT2: I started to solve this (a few weeks later), being pretty explicit regarding data types and having several data types per semantic data unit. For example, if the database contains X , my code has XAction to represent the objects I want to do with X and XResponse to relay X to the http client. And then I need to create supporting code for dragging bits between instances. Not perfect, but I like that it is explicit, and I hope that when my models crystallize, in fact it should not be very dependent and should be very reliable.


I am not sure what is the right level of abstraction to solve this problem (e.g. records? Or Yesod?). So I just lay out a simple case.

Simple Case / TL; DR

I want to decode the request body into type

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

but I don’t really want the request body to contain userid , the server will supply it based on its Auth headers (or wherever I would like to get the default data, fill in the field).

So they really pass me something like:

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

And I turn it into Comment . But simultaneously saving both almost identical types is a hassle, not DRY.

How to solve this problem?


Problem Details

I have a post type:

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

I have a POST route:

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

Note that the route requires the user to supply their userid (in the Comment type, which is at least redundant because their identifier is associated with their header headers. In the worst case, this means that I need to check that users add their own identifier or discard their provided identifier, in which case why do they supply it in the first case.

So, I need a post type that Comment minus userid , but I don't know how to do this wisely.


My current (awful but working) solution

So, I created my own type with a derived FromJSON (for the request body), which is almost completely redundant with the Comment type.

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

Then my new route should decode the request body according to this, and then combine a SimpleComment with the userid field to make it Comment :

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

Talk about the template. And my use case is more complex than this simple Comment type.

If it does, you might be able to say I'm using Yesod Scaffolding .

+7
haskell yesod aeson
source share
3 answers

The solution I like is to use a wrapper for things that come from / to the database:

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

Then you can have Comment just SimpleComment and turn it into Authenticated Comment when you know the user ID.

+2
source share

What I usually do to get a type minus a field is just a function that takes this field and returns the type. In your case, you just need to declare a JSON instance for UserId -> Comment . Well, this does not seem natural, and you need to go manually, but in fact it works very well, especially since there is only one field of type UserId in the comment.

+3
source share

I am also looking for a good way to solve this problem. :-)

What I usually do in my code is to work directly with the Aeson Value type. This is part of the sample code taken from my current project:

 import qualified Data.HashMap.Strict as HM removeKey :: Text -> Value -> Value removeKey key (Object xs) = Object $ HM.delete key xs removeKey _ ys = ys 

I use the Object value directly and delete the specific key present in the javascript object.

And in the Yesod handler code, I do this processing:

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

In some cases, I really want to add some specific key to the outgoing JSON object. For them, I have certain helper functions that work with the Value type. Although not perfect, it helped me avoid a lot of patterns.

0
source share

All Articles