5
votes

Today I wanted to solve next problem.

Assume that we have typeclass DataWithDefault defined as

class DataWithDefault a where
  defaultValue :: a

And we have data Example defined as

data Example =
  Example { field1 :: Text
          , field2 :: Text
          } deriving (Show)

instance DataWithDefault Example where
  defaultValue = Example "Hello" "World"

instance FromJSON Example where
  parseJSON (Object v) =
    Example <$> v .:? "field1" .!= field1 defaultValue
            <*> v .:? "field2" .!= field2 defaultValue
  parseJSON _ = mzero

instance ToJSON Example where
 toJSON (Example f1 f2)  =
    object [ "field1" .= f1
           , "field2" .= f2
           ]

I know that Aeson uses Generics to derive FromJSON and ToJSON instances automatically, but I can't figure out how to make it derive FromJSON instance with default values for fields that are not represented in given json. Is it possible to do using generics? Actually I don't ask you a final solution, but maybe some clue?

Update

Let me add more information about the problem.

Suppose now that you need to update your Example data and now it defined as

data Example =
  Example { field1 :: Text
          , field2 :: Text
          , field3 :: Int
          } deriving (Show)

So you want to update DataWithDefault instance declaration

instance DataWithDefault Example where
  defaultValue = Example "Hello" "World" 12

And what I want to do is not to write

instance FromJSON Example where
  parseJSON (Object v) =
    Example <$> v .:? "field1" .!= field1 defaultValue
            <*> v .:? "field2" .!= field2 defaultValue
            <*> v .:? "field3" .!= field3 defaultValue
  parseJSON _ = mzero

And want to derive such instance definition automatically. And more importantly, I want to do it not only for Example, but for DataWithDefault a.

Update 2

The point of combining .:? and .!= is to get as much as possible fields from given json and set every missing field to it's default value. So when we pass

{ "field1" : "space", "field2" : "ship" }

I want my new example be not field1 = Hello; field2 = World; field3 = 12, but field1 = space; field2 = ship; field3 = 12.

1
Couldn't you do fromMaybe defaultValue $ decode jsonContents?bheklilr
If you're making your own typeclass you might consider using instead Data.Default to get instances for a lot of structuresalternative

1 Answers

3
votes

Instead of making Aeson do it, just use a newtype for what they were designed for:

newtype DefaultJSON a = DefaultJSON { unDefaultJSON :: a }

instance (FromJSON a, DataWithDefault a) => FromJSON (DefaultJSON a) where
    parseJSON v = DefaultJSON <$> (parseJSON v <|> pure defaultValue)

Then you can do

> decode "{}" :: Maybe (DefaultJSON Example)
Just (DefaultJSON {unDefaultJSON = (Example {field1 = "Hello", field2 = "World"}})

This is a little different than what you asked, it provides a default value in case parsing fails, but not a default value for each field in case that individual field is missing.