{-|
There are two ways to use this library: the way with extra boilerplate,
or the way with a leaky abstraction. A truly good solution would fix it
all with Template Haskell; but the author is not a truly good Haskeller,
so this will have to do for now.

The boilerplate way:

@
data Hideous = Hideous { yak :: [String] }
instance ToJSON Hideous where
  toJSON x = object [ "yak" .= hairy (yak x) ]
instance FromJSON Hideous where
  parseJSON (Object o) = Hideous \<$> (shave \<$> o .:? "yak")
@
>>> encode $ Hideous ["foo","bar"]
"{\"yak\":[\"foo\",\"bar\"]}"
>>> encode $ Hideous ["baz"]
"{\"yak\":\"baz\"}"
>>> encode $ Hideous []
"{\"yak\":null}"
>>> yak <$> decode "{\"yak\":[\"foo\",\"bar\"]}"
Just ["foo", "bar"]
>>> yak <$> decode "{\"yak\":\"baz\"}"
Just ["baz"]
>>> yak <$> decode "{}"
Just []

The leaky way:

@
data Abhorrent = Abhorrent { yak :: Yak String }
$(deriveJSON defaultOptions{omitNothingFields=True} ''Abhorrent)
@
>>> encode . Abhorrent . hairy $ ["shaven","shorn","sheared"]
"{\"yak\":[\"shaven\",\"shorn\",\"sheared\"]}"
>>> shave . yak <$> decode "{}"
Just []

Which to prefer depends on how many yaks you need to deal with /vs./ how
much you hate cleaning up yak droppings in the rest of your codebase.
-}
module Data.Aeson.Yak
( Yak
, hairy
, shave)
where

import           Data.Aeson
import           Data.Foldable

-- | Data whose JSON representation may legally be an array, a single element,
--   or null\/absent. No, please, calm down. It'll be okay. Mostly.
--
--   /('Lousy' is not exposed to avoid namespace infestation./
--   /This is open for discussion if a use case can be shown.)/
type Yak a = Maybe (Lousy a)

-- | Convert a @'Foldable'@ to a 'Yak'. Should probably be specific to lists,
--   but what's life without a little adventure?
hairy :: (Foldable f) => f a -> Yak a
hairy beast = case toList beast of
  []  -> Nothing
  yak -> Just (Lousy yak)

-- | Convert a 'Yak' to a list. Relax, and allow yourself to breathe.
shave :: Yak a -> [a]
shave Nothing    = []
shave (Just nit) = pick nit

newtype Lousy a = Lousy { pick :: [a] } deriving (Eq)
instance (Show a) => Show (Lousy a) where
  show = ("Lousy " ++) . show . pick

instance (ToJSON a) => ToJSON (Lousy a) where
  toJSON nit = case pick nit of
    [x] -> toJSON x
    xs  -> toJSON xs

instance (FromJSON a) => FromJSON (Lousy a) where
  parseJSON vs@(Array _) = Lousy <$> parseJSON vs
  parseJSON v            = (\a -> Lousy [a]) <$> parseJSON v