{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module JSONDecodeSpec where import Control.Applicative import qualified Data.Aeson.Combinators.Decode as JD import Data.Aeson.Types (FromJSON (..)) import Data.ByteString.Lazy import Data.ByteString.Lazy.UTF8 (fromString) import Data.Text import GHC.Generics import Test.Hspec data Object = Object { name :: Text , nick :: Text } deriving (Show, Eq, Generic) data FooBar = Foo | Bar deriving (Show, Eq) instance FromJSON Object decoder :: JD.Decoder Object decoder = Object <$> JD.key "name" JD.text <*> JD.key "nick" JD.text json :: ByteString json = "{\"name\":\"Jany Doe\",\"nick\": \"jany\"}" jsonNested :: ByteString jsonNested = "{\"data\":" <> json <> "}" objectSpec :: Spec objectSpec = do describe "field" $ do let res = Just $ Object "Jany Doe" "jany" it "should decode object" $ do JD.decode decoder json `shouldBe` res it "should decode nested object" $ do JD.decode (JD.key "data" decoder) jsonNested `shouldBe` res it "should be possible to use default decoder" $ do JD.decode JD.auto json `shouldBe` res describe "at" $ do let nest' :: Int -> ByteString -> ByteString nest' n inner = if n <= 0 then inner else nest' (n - 1) ("{\"level-" <> fromString (show n) <> "\":" <> inner <> "}") let nest n = nest' n json let res = Just $ Object "Jany Doe" "jany" it "should decode object directly for empty list" $ do JD.decode (JD.at [] decoder) json `shouldBe` res it "should decode 2 level json" $ do let jdata = nest 2 JD.decode (JD.at ["level-1", "level-2"] decoder) jdata `shouldBe` res it "should decode 10 level json" $ do let jdata = nest 10 let path = ["level-1", "level-2", "level-3", "level-4", "level-5" , "level-6", "level-7", "level-8", "level-9", "level-10" ] JD.decode (JD.at path decoder) jdata `shouldBe` res arraySpec :: Spec arraySpec = do describe "index" $ do it "decode singleton by index" $ do JD.decode (JD.index 0 decoder) "[{\"name\": \"Sanders\", \"nick\": \"bern\"}]" `shouldBe` Just (Object "Sanders" "bern") it "should decode nth index" $ do JD.decode (JD.index 1 decoder) "[false, {\"name\": \"Sanders\", \"nick\": \"bern\"}, true]" `shouldBe` Just (Object "Sanders" "bern") monadSpec :: Spec monadSpec = describe "monadic decoding" $ do let fromText v = case v of "foo" -> pure Foo "bar" -> pure Bar _ -> fail "unknown" it "should work as a dummy value" $ do JD.decode (JD.string >>= pure) "\"foo\"" `shouldBe` Just "foo" it "should turn string to sum" $ do JD.decode (JD.string >>= fromText) "\"foo\"" `shouldBe` Just Foo it "should fail with right error" $ do JD.eitherDecode (JD.string >>= fromText) "\"foobar\"" `shouldBe` Left "Error in $: unknown" alternativeSpec :: Spec alternativeSpec = describe "alternative (<|>)" $ do let (dec :: JD.Decoder (Either Bool Object)) = Left <$> JD.bool <|> Right <$> decoder it "should decode first alternative" $ do JD.decode dec "false" `shouldBe` Just (Left False) it "should decode second alternative" $ do JD.decode dec "{\"name\": \"Joe\",\"nick\": \"jd\"}" `shouldBe` Just (Right $ Object "Joe" "jd") jsonNullSpec :: Spec jsonNullSpec = describe "jsonNull" $ do let barDec txt = case txt of "bar" -> pure Bar _ -> fail $ "Unknown value" <> show txt let (dec :: JD.Decoder FooBar) = JD.jsonNull Foo <|> (barDec =<< JD.text) it "should decode Foo from null" $ do JD.decode dec "null" `shouldBe` Just Foo it "should decode Bar from string" $ do JD.decode dec "\"bar\"" `shouldBe` Just Bar spec :: Spec spec = do objectSpec arraySpec monadSpec alternativeSpec jsonNullSpec