packages feed

aeson-combinators-0.1.2.2: test/JSONDecodeSpec.hs

{-# 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 (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