toddmohney / json-api

Haskell Implementation of the JSON-API specification
http://hackage.haskell.org/package/json-api
MIT License
35 stars 14 forks source link

Sparse fieldsets #32

Open karls opened 7 years ago

karls commented 7 years ago

Hi @toddmohney,

I'm investigating various structured approaches to building a public API for our web service. json-api seems like a good way to do it!

I'm wondering, does this library support sparse fieldsets in any way? I couldn't find an obvious way to do it.

Thanks!

toddmohney commented 7 years ago

Hey @karls I believe sparse fieldsets should be possible by defining your datatype with Maybe fields, then hydrating it appropriately based on the query params found in the request. If we look at the datatype used in one of the provided examples, we see User defined as

data User = User
  { userId        :: Int
  , userFirstName :: String
  , userLastName  :: String
  } deriving (Eq, Show)

$(deriveJSON defaultOptions ''User)

To support sparse fieldsets, I could change this definition to be something like

data User = User
  { userId        :: Int
  , userFirstName :: Maybe String
  , userLastName  :: Maybe String
  } deriving (Eq, Show)

-- this bit will hide the Nothings in the resulting JSON
userOptions :: Options
userOptions = defaultOptions { omitNothingFields = True }

$(deriveJSON userOptions ''User)

Then, based on the params found in the request, fill in the Maybes appropriately. Aeson already has options for omitting Nothing fields from its JSON output.

Long story short, I think sparse fieldsets are outside of the scope of this library and more a responsibility of your supplied datatype.

Let me know if this approach sounds like it will work for you. Thanks for checking out this library!

karls commented 6 years ago

Hey @toddmohney

Sorry for the delay in getting back to you — I became a parent and have been away from my computer for a few weeks. 🎉

Yeah, your outlined solution would work. I suppose there would be two records for the User entity — one that corresponds to the row in the database and one that represents an API response.

It'd be awsome to be able to generate an "API response" record from the "database" record. Something like

data User
  = User
  { userId        :: Int
  , userEmail     :: Text
  , userPassword  :: Text
  , userFirstName :: Text
  , userLastName  :: Text
  } deriving (Eq, Show, Generic)

$(deriveResponseRecord ["userEmail", "userFirstName", "userLastName"] ''User)

Which would generate something like this. The field names would have to be slightly different and the ToJSON instance would need to make sure that the field name prefix is dropped properly.

data User'
  = User'
  { userId        :: Int
  , userEmail     :: Maybe Text
  , userFirstName :: Maybe Text
  , userLastName  :: Maybe Text
  } deriving (Eq, Show, Generic)

instance ToJSON User'

I might look into how to do this with Template Haskell, if time permits.

toddmohney commented 6 years ago

Hi @karls

Yeah, it's likely that you'd end up with 2 models for the User; one for the data model, and another for the JSON API representation. I'm a big fan of the layered architecture approach, so modelling the User in terms of it's concerns (data storage/presentation) has worked out well for me in the past. Reducing boilerplate with a little Template Haskell is certainly an option, too.

Congratulations on your new baby! I hope your family is happy, healthy, and getting some rest! I have a 5 month old at home, so I'm a new dad as well :smile: