Code Monkey home page Code Monkey logo

aeson-injector's Introduction

aeson-injector

Build Status

It is small utility library that is intented to be used in RESTful APIs, especially with servant and Swagger. Its main purpose is simple injection of fields into JSONs produced by aeson library.

Consider the following common data type in web service developing:

data News = News {
  title :: Text
, body :: Text
, author :: Text
, timestamp :: UTCTime  
}

-- Consider we have simple 'ToJSON' and 'FromJSON' instances
$(deriveJSON defaultOptions ''News) 

ToJSON instance produces JSON's like:

{
  "title": "Awesome piece of news!"
, "body": "Big chunk of text"
, "author": "Just Me"
, "timestamp": "2016-07-26T18:54:42.678999Z"
}

Now one can create a simple web server with servant DSL:

type NewsId = Word 

type NewsAPI = 
       ReqBody '[JSON] News :> Post '[JSON] NewsId
  :<|> Capture "news-id" NewsId :> Get '[JSON] News
  :<|> "list" :> Get '[JSON] [News]

All seems legit, but, wait a second, an API user definitely would like to know id of news in list method. One way to do this is declare new type NewsInfo with additional field, but it is bad solution as requires to keep extra data type for each resource.

So, here aeson-injector steps in, now you can write:

type NewsAPI = 
       ReqBody '[JSON] News :> Post '[JSON] NewsId
  :<|> Capture "news-id" NewsId :> Get '[JSON] News
  :<|> "list" :> Get '[JSON] [WithField "id" NewsId News]

WithField "id" NewsId News or simply WithId NewsId News wraps you data type and injects id field in produced JSON values:

>>> encode (WithField 42 myNews :: WithField "id" NewsId News)
{
  "id": 42
, "title": "Awesome piece of news!"
, "body": "Big chunk of text"
, "author": "Just Me"
, "timestamp": "2016-07-26T18:54:42.678999Z"
}

WithField data type has FromJSON instance for seamless parsing of data with injected fields and ToSchema instance for servant-swagger support.

Injecting multiple values

The library also has more general data type WithFields a b that injects fields of 'toJSON a' into toJSON b.

data NewsPatch = NewsPatch {
  tags :: [Text]
, rating :: Double
}
$(deriveJSON defaultOptions ''NewsPatch) 
let myNewsPatch = NewsPatch ["tag1", "tag2"] 42 
in encode $ WithFields myNewsPatch myNews
{
  "title": "Awesome piece of news!"
, "body": "Big chunk of text"
, "author": "Just Me"
, "timestamp": "2016-07-26T18:54:42.678999Z"
, "tags": ["tag1", "tag2"]
, "rating": 42.0
}

Corner cases

Unfortunately, we cannot inject in non object values of produced JSON, so the library creates a wrapper object around non-object value:

encode (WithId 0 "non-object" :: WithId Int String)
{
  "id": 0 
, "value": "non-object"
}

The same story is about WithFields data type:

encode (WithFields 0 "non-object" :: WithFields Int String)
{
  "injected": 0 
, "value": "non-object"
}

For more examples and details, please, follow the haddocks.

aeson-injector's People

Contributors

andys8 avatar doctorryner avatar harendra-kumar avatar maksbotan avatar ncrashed avatar qnikst avatar swamp-agr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

aeson-injector's Issues

WithField with a Map inside fails JSON round-trip

import qualified Data.Map as Map

roundtrip :: (FromJSON a, ToJSON a) => a -> Either String a
roundtrip = parseEither parseJSON . toJSON

testvalue :: WithField "foo" Int (Map.Map Bool Int)
testvalue = WithField 10 (Map.fromList [(True, 20), (False, 30)])

Expected result: roundtrip testvalue == Right testvalue

Actual result:

> roundtrip testvalue
Left "Error in $: key \"value\" not present"

WithField injects foo into the JSON object, but fails to take it off during parsing before passing the value to the inner type. Map fails to parse foo as a boolean, so WithFields tries the wrapper case, but that also fails with the error message shown.

A fix would remove the injected field from the JSON object before calling parseJSON on the inner type here: https://github.com/NCrashed/aeson-injector/blob/master/src/Data/Aeson/WithField.hs#L246

Confusing parsing error when parsing WithFields

When using WithFields a b, parsing can fail with a very confusing and unexpected error like this one:

"Error in $: key \"value\" not present"

This happens when b can't be parsed successfully (e.g. when one of the fields can't be decoded or is missing).

I think that the error message is not helpful at all here.

If I understand correctly these "value" and "injected" special fields were introduced merely to

  • make toJSON total (work for non-object JSON representations)
  • make parseJSON . toJSON == Right (everything that can be encoded should be decoded)

However, I find the purpose of WithFields to be used exclusively with Objects.
As such I propose changing parseJSON this simple implementation:

instance (FromJSON a, FromJSON b) => FromJSON (WithFields a b) where
  parseJSON js = WithFields <$> parseJSON js <*> parseJSON js

If you don't want to break parseJSON for WithFields then I suggest introducing a new data type with that definition.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.