{-# LANGUAGE BlockArguments #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} module Main where import Data.Aeson (FromJSON(parseJSON), ToJSON(toJSON), Result(Success), fromJSON) import Data.Aeson.QQ.Simple (aesonQQ) import Data.Override (Override(Override), As, With) import Data.Override.Aeson () import Data.Text (Text) import GHC.Generics (Generic) import LispCaseAeson (LispCase(LispCase)) import qualified Data.Text as Text import Test.Hspec import Text.Read (readMaybe) main :: IO () main = hspec do describe "Override ToJSON machinery" do it "Rec1" testRec1 it "Rec2" testRec2 it "Rec3" testRec3 it "Rec4" testRec4 it "Rec5" testRec5 it "Rec6" testRec6 it "Rec7" testRec6 newtype Uptext = Uptext { unUptext :: Text } instance ToJSON Uptext where toJSON = toJSON . Text.toUpper . unUptext newtype Shown a = Shown { unShown :: a } instance (Show a) => ToJSON (Shown a) where toJSON = toJSON . show . unShown instance (Read a) => FromJSON (Shown a) where parseJSON v = do s <- parseJSON v case readMaybe s of Nothing -> fail "read: no parse" Just a -> pure $ Shown a newtype CharArray s = CharArray { unCharArray :: s } deriving stock (Functor) instance ToJSON (CharArray String) where toJSON = toJSON . map (:[]) . unCharArray instance FromJSON (CharArray String) where parseJSON v = do cs :: [String] <- parseJSON v pure $ CharArray $ concat cs instance ToJSON (CharArray Text) where toJSON = toJSON . fmap Text.unpack -- | Overriding instances by type. data Rec1 = Rec1 { foo :: Int , bar :: String , baz :: Text } deriving stock (Show, Eq, Generic) deriving (ToJSON) via Override Rec1 '[ Text `As` Uptext , Int `As` Shown Int ] testRec1 :: IO () testRec1 = do let r = Rec1 { foo = 12, bar = "hi", baz = "bye" } toJSON r `shouldBe` [aesonQQ| { "foo": "12", "bar": "hi", "baz": "BYE" } |] -- | Overriding instances by field name. data Rec2 = Rec2 { foo :: Int , bar :: Text , baz :: Text } deriving stock (Show, Eq, Generic) deriving (ToJSON) via Override Rec2 '[ "baz" `As` CharArray Text , "bar" `As` Uptext ] testRec2 :: IO () testRec2 = do let r = Rec2 { foo = 12, bar = "hi", baz = "bye" } toJSON r `shouldBe` [aesonQQ| { "foo": 12, "bar": "HI", "baz": ["b", "y", "e"] } |] -- | Overriding instances by type and field name. data Rec3 = Rec3 { foo :: Int , bar :: String , baz :: Text } deriving stock (Show, Eq, Generic) deriving (ToJSON) via Override Rec3 '[ String `As` CharArray String , "foo" `As` Shown Int , Text `As` Uptext ] testRec3 :: IO () testRec3 = do let r = Rec3 { foo = 12, bar = "hi", baz = "bye" } toJSON r `shouldBe` [aesonQQ| { "foo": "12", "bar": ["h", "i"], "baz": "BYE" } |] -- | Overriding instance by type and field name; first match wins. -- In this case, 'foo' and 'bar' use 'CharArray Text' and 'baz' uses 'Uptext'. data Rec4 = Rec4 { foo :: Text , bar :: Text , baz :: Text } deriving stock (Show, Eq, Generic) deriving (ToJSON) via Override Rec4 '[ "baz" `As` Uptext , Text `As` CharArray Text ] testRec4 :: IO () testRec4 = do let r = Rec4 { foo = "go", bar = "hi", baz = "bye" } toJSON r `shouldBe` [aesonQQ| { "foo": ["g", "o"], "bar": ["h", "i"], "baz": "BYE" } |] -- We can compose an 'Override' with another deriving-via newtype, 'LispCase'. data Rec5 = Rec5 { fooBar :: Int , baz :: Text , quuxSpamEggs :: String } deriving stock (Show, Eq, Generic) deriving (ToJSON) via LispCase (Override Rec5 '[ "fooBar" `As` Shown Int , Text `As` Uptext , "quuxSpamEggs" `As` CharArray String ]) testRec5 :: IO () testRec5 = do let r = Rec5 { fooBar = 1, baz = "hi", quuxSpamEggs = "bye" } toJSON r `shouldBe` [aesonQQ| { "foo-bar": "1", "baz": "HI", "quux-spam-eggs": ["b", "y", "e"] } |] -- Test 'Override' for both 'ToJSON' and 'FromJSON'. data Rec6 = Rec6 { foo :: Int , bar :: String , baz :: Text } deriving stock (Show, Eq, Generic) deriving (ToJSON, FromJSON) via Override Rec6 '[ "foo" `As` Shown Int , String `As` CharArray String ] testRec6 :: IO () testRec6 = do let r = Rec6 { foo = 1, bar = "hi", baz = "bye" } let j = [aesonQQ| { "foo": "1", "bar": ["h", "i"], "baz": "bye" } |] toJSON r `shouldBe` j fromJSON j `shouldBe` Success r -- Test 'Override' for both 'ToJSON' and 'FromJSON'. data Rec7 = Rec7 { foo :: Int , bar :: String , baz :: Text } deriving stock (Show, Eq, Generic) deriving (ToJSON, FromJSON) via Override Rec7 '[ "foo" `With` Shown , String `With` CharArray ] testRec7 :: IO () testRec7 = do let r = Rec7 { foo = 1, bar = "hi", baz = "bye" } let j = [aesonQQ| { "foo": "1", "bar": ["h", "i"], "baz": "bye" } |] toJSON r `shouldBe` j fromJSON j `shouldBe` Success r