Override how Data.Aeson handles only one field of

2019-06-02 02:00发布

问题:

I am making a REST API for university courses:

data Course = Course {
    id :: Maybe Text,
    name :: Text,
    deleted :: Bool
} deriving(Show, Generic)

instance FromJSON Course
instance ToJSON Course

I would like to allow deleted to be optional in the serialized JSON structure, but not in my application. I want to set deleted to False if it isn't specified when parsing.

I could write a manual instance for FromJSON, but I don't want to have to write it out for all the fields. I want to declare how deleted is handled and let the automatic instance handle everything else.

How would I do this?

回答1:

To my knowledge there is not a way to customize the generic instance, but you could structure your type a bit differently:

data Course = Course
    { courseId :: Maybe Text    -- Don't use `id`, it's already a function
    , name :: Text
    } deriving (Show, Generic)

data Deletable a = Deletable
    { deleted :: Bool
    , item :: a
    } deriving (Show)

instance FromJSON Course
instance ToJSON Course

instance FromJSON a => FromJSON (Deletable a) where
    parseJSON (Object v) = do
        i <- parseJSON (Object v)
        d <- v .:? "deleted" .!= False
        return $ Deletable d i
    parseJSON _ = mzero

Now you can do

> let noDeleted = "{\"name\":\"Math\",\"courseId\":\"12345\"}" :: Text
> let withDeleted = "{\"name\":\"Math\",\"courseId\":\"12345\",\"deleted\":true}" :: Text

> decode noDeleted :: Maybe (Deletable Course)
Just (Deletable {deleted = False, item = Course {courseId = Just "12345", name = "Math"}})

> decode noDeleted :: Maybe Course
Just (Course {courseId = Just "12345", name = "Math"})

> decode withDeleted :: Maybe (Deletable Course)
Just (Deletable {deleted = True, item = Course {courseId = Just "12345", name = "Math"}})

> decode withDeleted :: Maybe Course
Just (Course {courseId = Just "12345", name = "Math"})

And now you can just optionally tag a course as deletable when you need it, and the FromJSON instances take care of everything.