2{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE TemplateHaskell #-}
8#if __GLASGOW_HASKELL__ >= 708
9{-# LANGUAGE DataKinds #-}
12{-# OPTIONS_GHC -fno-warn-deprecations #-}
23import Control.Applicative (Const(..))
24import Control.Monad (forM, forM_, unless)
25import Data.Aeson ((.=), (.:), (.:?), (.:!), FromJSON(..), FromJSONKeyFunction(..), FromJSONKey(..), ToJSON1(..), decode, eitherDecode, encode, genericParseJSON, genericToEncoding, genericToJSON, object, withObject)
26import Data.Aeson.Internal (JSONPathElement(..), formatError)
27import Data.Aeson.TH (deriveJSON, deriveToJSON, deriveToJSON1)
28import Data.Aeson.Text (encodeToTextBuilder)
29import Data.Aeson.Types (Options(..), SumEncoding(..), ToJSON(..), Value, camelTo, camelTo2, defaultOptions, omitNothingFields)
30import Data.Char (toUpper)
31import Data.Either.Compat (isLeft, isRight)
32import Data.Fixed (Pico)
33import Data.Functor.Compose (Compose(..))
34import Data.Functor.Identity (Identity(..))
35import Data.Functor.Product (Product(..))
36import Data.Functor.Sum (Sum(..))
37import Data.Hashable (hash)
38import Data.List (sort)
39import Data.List.NonEmpty (NonEmpty(..))
40import Data.Maybe (fromMaybe)
41import Data.Proxy (Proxy(..))
42import Data.Scientific (Scientific)
43import Data.Sequence (Seq)
44import Data.Tagged (Tagged(..))
45import Data.Text (Text)
46import Data.Time (UTCTime, fromGregorian)
47import Data.Time.Format (parseTime)
48import Data.Time.Locale.Compat (defaultTimeLocale)
49import Data.Word (Word8)
50import GHC.Generics (Generic)
52import System.Directory (doesDirectoryExist, getDirectoryContents)
53import System.Exit (exitWith, ExitCode(ExitFailure))
54import System.IO (hPutStrLn, stderr)
55import System.FilePath ((</>), takeExtension, takeFileName)
56import Test.Framework (Test, testGroup)
57import Test.Framework.Providers.HUnit (testCase)
58import Test.HUnit (Assertion, assertBool, assertFailure, assertEqual)
59import Text.Printf (printf)
60import Types (Approx(..), Compose3, Compose3', I)
61import UnitTests.NullaryConstructors (nullaryConstructors)
62import qualified Data.ByteString.Base16.Lazy as LBase16
63import qualified Data.ByteString.Lazy.Char8 as L
64import qualified Data.DList as DList
65import qualified Data.HashMap.Strict as HM
66import qualified Data.HashSet as HashSet
67import qualified Data.IntMap as IntMap
68import qualified Data.IntSet as IntSet
69import qualified Data.Map as M
70import qualified Data.Monoid as Monoid
71import qualified Data.Semigroup as Semigroup
72import qualified Data.Sequence as Seq
73import qualified Data.Set as Set
74import qualified Data.Text.Lazy as LT
75import qualified Data.Text.Lazy.Builder as TLB
76import qualified Data.Text.Lazy.Encoding as LT
77import qualified Data.Text.Lazy.Encoding as TLE
78import qualified Data.Tree as Tree
79import qualified Data.UUID.Types as UUID
80import qualified Data.Vector as Vector
83tests = testGroup "unit" [
84 testGroup "camelCase" [
85 testCase "camelTo" $ roundTripCamel "aName"
86 , testCase "camelTo" $ roundTripCamel "another"
87 , testCase "camelTo" $ roundTripCamel "someOtherName"
88 , testCase "camelTo" $
89 assertEqual "" "camel_apicase" (camelTo '_' "CamelAPICase")
90 , testCase "camelTo2" $ roundTripCamel2 "aName"
91 , testCase "camelTo2" $ roundTripCamel2 "another"
92 , testCase "camelTo2" $ roundTripCamel2 "someOtherName"
93 , testCase "camelTo2" $
94 assertEqual "" "camel_api_case" (camelTo2 '_' "CamelAPICase")
96 , testGroup "encoding" [
97 testCase "goodProducer" $ goodProducer
99 , testGroup "utctime" [
100 testCase "good" $ utcTimeGood
101 , testCase "bad" $ utcTimeBad
103 , testGroup "formatError" [
104 testCase "example 1" $ formatErrorExample
106 , testGroup ".:, .:?, .:!" $ fmap (testCase "-") dotColonMark
107 , testGroup "To JSON representation" $ fmap assertJsonEncodingExample jsonEncodingExamples
108 , testGroup "From JSON representation" $ fmap assertJsonExample jsonDecodingExamples
109 , testGroup "To/From JSON representation" $ fmap assertJsonExample jsonExamples
110 , testGroup "JSONPath" $ fmap (testCase "-") jsonPath
111 , testGroup "Hashable laws" $ fmap (testCase "-") hashableLaws
112 , testGroup "Issue #351" $ fmap (testCase "-") issue351
113 , testGroup "Nullary constructors" $ fmap (testCase "-") nullaryConstructors
114 , testGroup "FromJSONKey" $ fmap (testCase "-") fromJSONKeyAssertions
115 , testCase "PR #455" pr455
116 , testCase "Unescape string (PR #477)" unescapeString
119roundTripCamel :: String -> Assertion
120roundTripCamel name = assertEqual "" name (camelFrom '_' $ camelTo '_' name)
122roundTripCamel2 :: String -> Assertion
123roundTripCamel2 name = assertEqual "" name (camelFrom '_' $ camelTo2 '_' name)
125camelFrom :: Char -> String -> String
126camelFrom c s = let (p:ps) = split c s
127 in concat $ p : map capitalize ps
129 split c' s' = map L.unpack $ L.split c' $ L.pack s'
130 capitalize t = toUpper (head t) : tail t
133data MyEither a b = MyLeft a | MyRight b
134 deriving (Generic, Show, Eq)
136instance (ToJSON a, ToJSON b) => ToJSON (MyEither a b) where
137 toJSON = genericToJSON defaultOptions { sumEncoding = UntaggedValue }
138 toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }
140instance (FromJSON a, FromJSON b) => FromJSON (MyEither a b) where
141 parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }
144data Wibble = Wibble {
145 wibbleString :: String
147 } deriving (Generic, Show, Eq)
149instance FromJSON Wibble
151instance ToJSON Wibble where
152 toJSON = genericToJSON defaultOptions
153 toEncoding = genericToEncoding defaultOptions
155-- Test that if we put a bomb in a data structure, but only demand
156-- part of it via lazy encoding, we do not unexpectedly fail.
157goodProducer :: Assertion
158goodProducer = assertEqual "partial encoding should not explode on undefined"
159 '{' (L.head (encode wibble))
162 wibbleString = replicate k 'a'
167 arch32bit = (maxBound :: Int) == 2147483647
169-- Test decoding various UTC time formats
171-- Note: the incomplete pattern matches for UTCTimes are completely
172-- intentional. The test expects these parses to succeed. If the
173-- pattern matches fails, there's a bug in either the test or in aeson
174-- and needs to be investigated.
175utcTimeGood :: Assertion
177 let ts1 = "2015-01-01T12:13:00.00Z" :: LT.Text
178 let ts2 = "2015-01-01T12:13:00Z" :: LT.Text
179 -- 'T' between date and time is not required, can be space
180 let ts3 = "2015-01-03 12:13:00.00Z" :: LT.Text
181 let ts4 = "2015-01-03 12:13:00.125Z" :: LT.Text
182 let (Just (t1 :: UTCTime)) = parseWithAeson ts1
183 let (Just (t2 :: UTCTime)) = parseWithAeson ts2
184 let (Just (t3 :: UTCTime)) = parseWithAeson ts3
185 let (Just (t4 :: UTCTime)) = parseWithAeson ts4
186 assertEqual "utctime" (parseWithRead "%FT%T%QZ" ts1) t1
187 assertEqual "utctime" (parseWithRead "%FT%T%QZ" ts2) t2
188 assertEqual "utctime" (parseWithRead "%F %T%QZ" ts3) t3
189 assertEqual "utctime" (parseWithRead "%F %T%QZ" ts4) t4
190 -- Time zones. Both +HHMM and +HH:MM are allowed for timezone
191 -- offset, and MM may be omitted.
192 let ts5 = "2015-01-01T12:30:00.00+00" :: LT.Text
193 let ts6 = "2015-01-01T12:30:00.00+01:15" :: LT.Text
194 let ts7 = "2015-01-01T12:30:00.00-02" :: LT.Text
195 let ts8 = "2015-01-01T22:00:00.00-03" :: LT.Text
196 let ts9 = "2015-01-01T22:00:00.00-04:30" :: LT.Text
197 let (Just (t5 :: UTCTime)) = parseWithAeson ts5
198 let (Just (t6 :: UTCTime)) = parseWithAeson ts6
199 let (Just (t7 :: UTCTime)) = parseWithAeson ts7
200 let (Just (t8 :: UTCTime)) = parseWithAeson ts8
201 let (Just (t9 :: UTCTime)) = parseWithAeson ts9
202 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T12:30:00.00Z") t5
203 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T11:15:00.00Z") t6
204 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T14:30:00Z") t7
205 -- ts8 wraps around to the next day in UTC
206 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-02T01:00:00Z") t8
207 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-02T02:30:00Z") t9
209 -- Seconds in Time can be omitted
210 let ts10 = "2015-01-03T12:13Z" :: LT.Text
211 let ts11 = "2015-01-03 12:13Z" :: LT.Text
212 let ts12 = "2015-01-01T12:30-02" :: LT.Text
213 let (Just (t10 :: UTCTime)) = parseWithAeson ts10
214 let (Just (t11 :: UTCTime)) = parseWithAeson ts11
215 let (Just (t12 :: UTCTime)) = parseWithAeson ts12
216 assertEqual "utctime" (parseWithRead "%FT%H:%MZ" ts10) t10
217 assertEqual "utctime" (parseWithRead "%F %H:%MZ" ts11) t11
218 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T14:30:00Z") t12
221 parseWithRead :: String -> LT.Text -> UTCTime
223 case parseTime defaultTimeLocale f . LT.unpack $ s of
224 Nothing -> error "parseTime input malformed"
226 parseWithAeson :: LT.Text -> Maybe UTCTime
227 parseWithAeson s = decode . LT.encodeUtf8 $ (LT.concat ["\"", s, "\""])
229-- Test that a few non-timezone qualified timestamp formats get
230-- rejected if decoding to UTCTime.
231utcTimeBad :: Assertion
233 verifyFailParse "2000-01-01T12:13:00" -- missing Zulu time not allowed (some TZ required)
234 verifyFailParse "2000-01-01 12:13:00" -- missing Zulu time not allowed (some TZ required)
235 verifyFailParse "2000-01-01" -- date only not OK
236 verifyFailParse "2000-01-01Z" -- date only not OK
237 verifyFailParse "2015-01-01T12:30:00.00+00Z" -- no Zulu if offset given
238 verifyFailParse "2015-01-01T12:30:00.00+00:00Z" -- no Zulu if offset given
239 verifyFailParse "2015-01-03 12:13:00.Z" -- decimal at the end but no digits
240 verifyFailParse "2015-01-03 12:13.000Z" -- decimal at the end, but no seconds
242 verifyFailParse (s :: LT.Text) =
243 let (dec :: Maybe UTCTime) = decode . LT.encodeUtf8 $ (LT.concat ["\"", s, "\""]) in
244 assertEqual "verify failure" Nothing dec
246-- Non identifier keys should be escaped & enclosed in brackets
247formatErrorExample :: Assertion
249 let rhs = formatError [Index 0, Key "foo", Key "bar", Key "a.b.c", Key "", Key "'\\", Key "end"] "error msg"
250 lhs = "Error in $[0].foo.bar['a.b.c']['']['\\'\\\\'].end: error msg"
251 in assertEqual "formatError example" lhs rhs
253------------------------------------------------------------------------------
254-- Comparison (.:?) and (.:!)
255------------------------------------------------------------------------------
257newtype T1 = T1 (Maybe Int) deriving (Eq, Show)
258newtype T2 = T2 (Maybe Int) deriving (Eq, Show)
259newtype T3 = T3 (Maybe Int) deriving (Eq, Show)
261instance FromJSON T1 where parseJSON = fmap T1 . withObject "T1" (.: "value")
262instance FromJSON T2 where parseJSON = fmap T2 . withObject "T2" (.:? "value")
263instance FromJSON T3 where parseJSON = fmap T3 . withObject "T3" (.:! "value")
265dotColonMark :: [Assertion]
267 assertEqual ".: not-present" Nothing (decode ex1 :: Maybe T1)
268 , assertEqual ".: 42" (Just (T1 (Just 42))) (decode ex2 :: Maybe T1)
269 , assertEqual ".: null" (Just (T1 Nothing)) (decode ex3 :: Maybe T1)
271 , assertEqual ".:? not-present" (Just (T2 (Nothing))) (decode ex1 :: Maybe T2)
272 , assertEqual ".:? 42" (Just (T2 (Just 42))) (decode ex2 :: Maybe T2)
273 , assertEqual ".:? null" (Just (T2 Nothing)) (decode ex3 :: Maybe T2)
275 , assertEqual ".:! not-present" (Just (T3 (Nothing))) (decode ex1 :: Maybe T3)
276 , assertEqual ".:! 42" (Just (T3 (Just 42))) (decode ex2 :: Maybe T3)
277 , assertEqual ".:! null" Nothing (decode ex3 :: Maybe T3)
280 ex2 = "{\"value\": 42 }"
281 ex3 = "{\"value\": null }"
283------------------------------------------------------------------------------
284-- These tests assert that the JSON serialization doesn't change by accident.
285-----------------------------------------------------------------------------
289 :: (Eq a, Show a, ToJSON a, FromJSON a)
290 => String -> L.ByteString -> a -> Example
292 :: (Eq a, Show a, FromJSON a)
293 => String -> L.ByteString -> Maybe a -> Example
295assertJsonExample :: Example -> Test
296assertJsonExample (Example name bs val) = testCase name $ do
297 assertEqual "encode" bs (encode val)
298 assertEqual "encode/via value" bs (encode $ toJSON val)
299 assertEqual "decode" (Just val) (decode bs)
300assertJsonExample (MaybeExample name bs mval) = testCase name $
301 assertEqual "decode" mval (decode bs)
303assertJsonEncodingExample :: Example -> Test
304assertJsonEncodingExample (Example name bs val) = testCase name $ do
305 assertEqual "encode" bs (encode val)
306 assertEqual "encode/via value" bs (encode $ toJSON val)
307assertJsonEncodingExample (MaybeExample name _ _) = testCase name $
308 assertFailure "cannot encode MaybeExample"
310jsonEncodingExamples :: [Example]
311jsonEncodingExamples =
313 -- Maybe serialising is lossy
314 -- https://github.com/bos/aeson/issues/376
315 Example "Just Nothing" "null" (Just Nothing :: Maybe (Maybe Int))
316 -- infinities cannot be recovered, null is decoded as NaN
317 , Example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)
320jsonDecodingExamples :: [Example]
321jsonDecodingExamples = [
322 -- Maybe serialising is lossy
323 -- https://github.com/bos/aeson/issues/376
324 MaybeExample "Nothing" "null" (Just $ Nothing :: Maybe (Maybe Int))
325 , MaybeExample "Just" "1" (Just $ Just 1 :: Maybe (Maybe Int))
326 , MaybeExample "Just Nothing" "null" (Just $ Nothing :: Maybe (Maybe (Maybe Int)))
327 -- Integral values are truncated, and overflowed
328 -- https://github.com/bos/aeson/issues/317
329 , MaybeExample "Word8 3" "3" (Just 3 :: Maybe Word8)
330 , MaybeExample "Word8 3.00" "3.00" (Just 3 :: Maybe Word8)
331 , MaybeExample "Word8 3.14" "3.14" (Nothing :: Maybe Word8)
332 , MaybeExample "Word8 -1" "-1" (Nothing :: Maybe Word8)
333 , MaybeExample "Word8 300" "300" (Nothing :: Maybe Word8)
334 -- Negative zero year, encoding never produces such:
335 , MaybeExample "Day -0000-02-03" "\"-0000-02-03\"" (Just (fromGregorian 0 2 3))
338jsonExamples :: [Example]
341 Example "Either Left" "{\"Left\":1}" (Left 1 :: Either Int Int)
342 , Example "Either Right" "{\"Right\":1}" (Right 1 :: Either Int Int)
343 , Example "Nothing" "null" (Nothing :: Maybe Int)
344 , Example "Just" "1" (Just 1 :: Maybe Int)
345 , Example "Proxy Int" "null" (Proxy :: Proxy Int)
346 , Example "Tagged Char Int" "1" (Tagged 1 :: Tagged Char Int)
347#if __GLASGOW_HASKELL__ >= 708
348 -- Test Tagged instance is polykinded
349 , Example "Tagged 123 Int" "1" (Tagged 1 :: Tagged 123 Int)
351 , Example "Const Char Int" "\"c\"" (Const 'c' :: Const Char Int)
352 , Example "Tuple" "[1,2]" ((1, 2) :: (Int, Int))
353 , Example "NonEmpty" "[1,2,3]" (1 :| [2, 3] :: NonEmpty Int)
354 , Example "Seq" "[1,2,3]" (Seq.fromList [1, 2, 3] :: Seq.Seq Int)
355 , Example "DList" "[1,2,3]" (DList.fromList [1, 2, 3] :: DList.DList Int)
356 , Example "()" "[]" ()
358 , Example "HashMap Int Int" "{\"0\":1,\"2\":3}" (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)
359 , Example "Map Int Int" "{\"0\":1,\"2\":3}" (M.fromList [(0,1),(2,3)] :: M.Map Int Int)
360 , Example "Map (Tagged Int Int) Int" "{\"0\":1,\"2\":3}" (M.fromList [(Tagged 0,1),(Tagged 2,3)] :: M.Map (Tagged Int Int) Int)
361 , Example "Map [Int] Int" "[[[0],1],[[2],3]]" (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)
362 , Example "Map [Char] Int" "{\"ab\":1,\"cd\":3}" (M.fromList [("ab",1),("cd",3)] :: M.Map [Char] Int)
363 , Example "Map [I Char] Int" "{\"ab\":1,\"cd\":3}" (M.fromList [(map pure "ab",1),(map pure "cd",3)] :: M.Map [I Char] Int)
365 , Example "nan :: Double" "null" (Approx $ 0/0 :: Approx Double)
367 , Example "Ordering LT" "\"LT\"" LT
368 , Example "Ordering EQ" "\"EQ\"" EQ
369 , Example "Ordering GT" "\"GT\"" GT
371 , Example "Float" "3.14" (3.14 :: Float)
372 , Example "Pico" "3.14" (3.14 :: Pico)
373 , Example "Scientific" "3.14" (3.14 :: Scientific)
375 , Example "UUID" "\"c2cc10e1-57d6-4b6f-9899-38d972112d8c\"" $ UUID.fromWords
376 0xc2cc10e1 0x57d64b6f 0x989938d9 0x72112d8c
378 , Example "Set Int" "[1,2,3]" (Set.fromList [3, 2, 1] :: Set.Set Int)
379 , Example "IntSet" "[1,2,3]" (IntSet.fromList [3, 2, 1])
380 , Example "IntMap" "[[1,2],[3,4]]" (IntMap.fromList [(3,4), (1,2)] :: IntMap.IntMap Int)
381 , Example "Vector" "[1,2,3]" (Vector.fromList [1, 2, 3] :: Vector.Vector Int)
382 , Example "HashSet Int" "[1,2,3]" (HashSet.fromList [3, 2, 1] :: HashSet.HashSet Int)
383 , Example "Tree Int" "[1,[[2,[[3,[]],[4,[]]]],[5,[]]]]" (let n = Tree.Node in n 1 [n 2 [n 3 [], n 4 []], n 5 []] :: Tree.Tree Int)
385 -- Three separate cases, as ordering in HashMap is not defined
386 , Example "HashMap Float Int, NaN" "{\"NaN\":1}" (Approx $ HM.singleton (0/0) 1 :: Approx (HM.HashMap Float Int))
387 , Example "HashMap Float Int, Infinity" "{\"Infinity\":1}" (HM.singleton (1/0) 1 :: HM.HashMap Float Int)
388 , Example "HashMap Float Int, +Infinity" "{\"-Infinity\":1}" (HM.singleton (negate 1/0) 1 :: HM.HashMap Float Int)
391 , Example "Identity Int" "1" (pure 1 :: Identity Int)
393 , Example "Identity Char" "\"x\"" (pure 'x' :: Identity Char)
394 , Example "Identity String" "\"foo\"" (pure "foo" :: Identity String)
395 , Example "[Identity Char]" "\"xy\"" ([pure 'x', pure 'y'] :: [Identity Char])
397 , Example "Maybe Char" "\"x\"" (pure 'x' :: Maybe Char)
398 , Example "Maybe String" "\"foo\"" (pure "foo" :: Maybe String)
399 , Example "Maybe [Identity Char]" "\"xy\"" (pure [pure 'x', pure 'y'] :: Maybe [Identity Char])
401 , Example "Day; year >= 1000" "\"1999-10-12\"" (fromGregorian 1999 10 12)
402 , Example "Day; year > 0 && < 1000" "\"0500-03-04\"" (fromGregorian 500 3 4)
403 , Example "Day; year == 0" "\"0000-02-20\"" (fromGregorian 0 2 20)
404 , Example "Day; year < 0" "\"-0234-01-01\"" (fromGregorian (-234) 1 1)
405 , Example "Day; year < -1000" "\"-1234-01-01\"" (fromGregorian (-1234) 1 1)
407 , Example "Product I Maybe Int" "[1,2]" (Pair (pure 1) (pure 2) :: Product I Maybe Int)
408 , Example "Product I Maybe Int" "[1,null]" (Pair (pure 1) Nothing :: Product I Maybe Int)
409 , Example "Product I [] Char" "[\"a\",\"foo\"]" (Pair (pure 'a') "foo" :: Product I [] Char)
411 , Example "Sum I [] Int: InL" "{\"InL\":1}" (InL (pure 1) :: Sum I [] Int)
412 , Example "Sum I [] Int: InR" "{\"InR\":[1,2]}" (InR [1, 2] :: Sum I [] Int)
413 , Example "Sum I [] Char: InR" "{\"InR\":\"foo\"}" (InR "foo" :: Sum I [] Char)
415 , Example "Compose I I Int" "1" (pure 1 :: Compose I I Int)
416 , Example "Compose I [] Int" "[1]" (pure 1 :: Compose I [] Int)
417 , Example "Compose [] I Int" "[1]" (pure 1 :: Compose [] I Int)
418 , Example "Compose [] [] Int" "[[1]]" (pure 1 :: Compose [] [] Int)
420 , Example "Compose I I Char" "\"x\"" (pure 'x' :: Compose I I Char)
421 , Example "Compose I [] Char" "\"x\"" (pure 'x' :: Compose I [] Char)
422 , Example "Compose [] I Char" "\"x\"" (pure 'x' :: Compose [] I Char)
423 , Example "Compose [] [] Char" "[\"x\"]" (pure 'x' :: Compose [] [] Char)
425 , Example "Compose3 I I I Char" "\"x\"" (pure 'x' :: Compose3 I I I Char)
426 , Example "Compose3 I I [] Char" "\"x\"" (pure 'x' :: Compose3 I I [] Char)
427 , Example "Compose3 I [] I Char" "\"x\"" (pure 'x' :: Compose3 I [] I Char)
428 , Example "Compose3 I [] [] Char" "[\"x\"]" (pure 'x' :: Compose3 I [] [] Char)
429 , Example "Compose3 [] I I Char" "\"x\"" (pure 'x' :: Compose3 [] I I Char)
430 , Example "Compose3 [] I [] Char" "[\"x\"]" (pure 'x' :: Compose3 [] I [] Char)
431 , Example "Compose3 [] [] I Char" "[\"x\"]" (pure 'x' :: Compose3 [] [] I Char)
432 , Example "Compose3 [] [] [] Char" "[[\"x\"]]" (pure 'x' :: Compose3 [] [] [] Char)
434 , Example "Compose3' I I I Char" "\"x\"" (pure 'x' :: Compose3' I I I Char)
435 , Example "Compose3' I I [] Char" "\"x\"" (pure 'x' :: Compose3' I I [] Char)
436 , Example "Compose3' I [] I Char" "\"x\"" (pure 'x' :: Compose3' I [] I Char)
437 , Example "Compose3' I [] [] Char" "[\"x\"]" (pure 'x' :: Compose3' I [] [] Char)
438 , Example "Compose3' [] I I Char" "\"x\"" (pure 'x' :: Compose3' [] I I Char)
439 , Example "Compose3' [] I [] Char" "[\"x\"]" (pure 'x' :: Compose3' [] I [] Char)
440 , Example "Compose3' [] [] I Char" "[\"x\"]" (pure 'x' :: Compose3' [] [] I Char)
441 , Example "Compose3' [] [] [] Char" "[[\"x\"]]" (pure 'x' :: Compose3' [] [] [] Char)
443 , Example "MyEither Int String: Left" "42" (MyLeft 42 :: MyEither Int String)
444 , Example "MyEither Int String: Right" "\"foo\"" (MyRight "foo" :: MyEither Int String)
446 -- newtypes from Monoid/Semigroup
447 , Example "Monoid.Dual Int" "2" (pure 2 :: Monoid.Dual Int)
448 , Example "Monoid.First Int" "2" (pure 2 :: Monoid.First Int)
449 , Example "Monoid.Last Int" "2" (pure 2 :: Monoid.Last Int)
450 , Example "Semigroup.Min Int" "2" (pure 2 :: Semigroup.Min Int)
451 , Example "Semigroup.Max Int" "2" (pure 2 :: Semigroup.Max Int)
452 , Example "Semigroup.First Int" "2" (pure 2 :: Semigroup.First Int)
453 , Example "Semigroup.Last Int" "2" (pure 2 :: Semigroup.Last Int)
454 , Example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)
455 , Example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)
456 , Example "Semigroup.Option Nothing" "null" (Semigroup.Option (Nothing :: Maybe Bool))
461------------------------------------------------------------------------------
462-- These tests check that JSONPath is tracked correctly
463-----------------------------------------------------------------------------
465jsonPath :: [Assertion]
469 (Left "Error in $[1].Left[1]: expected Bool, encountered Number")
470 (eitherDecode "[1,{\"Left\":[2,3]}]"
471 :: Either String (Int, Either (Int, Bool) ()))
473 , assertEqual "Seq a"
474 (Left "Error in $[2]: expected Int, encountered Boolean")
475 (eitherDecode "[0,1,true]" :: Either String (Seq Int))
476 , assertEqual "Wibble"
477 (Left "Error in $.wibbleInt: expected Int, encountered Boolean")
478 (eitherDecode "{\"wibbleString\":\"\",\"wibbleInt\":true}"
479 :: Either String Wibble)
482------------------------------------------------------------------------------
483-- Check that the hashes of two equal Value are the same
484------------------------------------------------------------------------------
486hashableLaws :: [Assertion]
488 assertEqual "Hashable Object" (hash a) (hash b)
491 a = object ["223" .= False, "807882556" .= True]
492 b = object ["807882556" .= True, "223" .= False]
494-------------------------------------------------------------------------------
496-------------------------------------------------------------------------------
498newtype MyText = MyText Text
499 deriving (FromJSONKey)
501newtype MyText' = MyText' Text
503instance FromJSONKey MyText' where
504 fromJSONKey = fmap MyText' fromJSONKey
505 fromJSONKeyList = error "not used"
507fromJSONKeyAssertions :: [Assertion]
508fromJSONKeyAssertions =
509 [ assertIsCoerce "Text" (fromJSONKey :: FromJSONKeyFunction Text)
510 , assertIsCoerce "Tagged Int Text" (fromJSONKey :: FromJSONKeyFunction (Tagged Int Text))
511 , assertIsCoerce "MyText" (fromJSONKey :: FromJSONKeyFunction MyText)
513#if __GLASGOW_HASKELL__ >= 710
514 , assertIsCoerce' "MyText'" (fromJSONKey :: FromJSONKeyFunction MyText')
518 assertIsCoerce _ (FromJSONKeyCoerce _) = pure ()
519 assertIsCoerce n _ = assertFailure n
521#if __GLASGOW_HASKELL__ >= 710
522 assertIsCoerce' _ (FromJSONKeyCoerce _) = pure ()
523 assertIsCoerce' n _ = pickWithRules (assertFailure n) (pure ())
525-- | Pick the first when RULES are enabled, e.g. optimisations are on
527 :: a -- ^ Pick this when RULES are on
528 -> a -- ^ use this otherwise
531{-# NOINLINE pickWithRules #-}
532{-# RULES "pickWithRules/rule" [0] forall x. pickWithRules x = const x #-}
535------------------------------------------------------------------------------
537------------------------------------------------------------------------------
539-- A regression test for: https://github.com/bos/aeson/issues/351
540overlappingRegression :: FromJSON a => L.ByteString -> [a]
541overlappingRegression bs = fromMaybe [] $ decode bs
543issue351 :: [Assertion]
545 assertEqual "Int" ([1, 2, 3] :: [Int]) $ overlappingRegression "[1, 2, 3]"
546 , assertEqual "Char" ("abc" :: String) $ overlappingRegression "\"abc\""
547 , assertEqual "Char" ("" :: String) $ overlappingRegression "[\"a\", \"b\", \"c\"]"
550------------------------------------------------------------------------------
551-- Comparison between bytestring and text encoders
552------------------------------------------------------------------------------
556 enc <- encoderComparisonTests
560encoderComparisonTests :: IO Test
561encoderComparisonTests = do
562 encoderTests <- forM testFiles $ \file0 -> do
563 let file = "benchmarks/json-data/" ++ file0
564 return $ testCase file $ do
565 inp <- L.readFile file
566 case eitherDecode inp of
567 Left err -> assertFailure $ "Decoding failure: " ++ err
568 Right val -> assertEqual "" (encode val) (encodeViaText val)
569 return $ testGroup "encoders" encoderTests
571 encodeViaText :: Value -> L.ByteString
573 TLE.encodeUtf8 . TLB.toLazyText . encodeToTextBuilder . toJSON
590-- A regression test for: https://github.com/bos/aeson/issues/293
591data MyRecord = MyRecord {_field1 :: Maybe Int, _field2 :: Maybe Bool}
593data MyRecord2 = MyRecord2 {_field3 :: Maybe Int, _field4 :: Maybe Bool}
596instance ToJSON MyRecord2
597instance FromJSON MyRecord2
599-- A regression test for: https://github.com/bos/aeson/pull/477
600unescapeString :: Assertion
602 assertEqual "Basic escaping"
603 (Right ("\" / \\ \b \f \n \r \t" :: String))
604 (eitherDecode "\"\\\" \\/ \\\\ \\b \\f \\n \\r \\t\"")
606 forM_ [minBound .. maxBound :: Char] $ \ c ->
607 let s = LT.pack [c] in
608 assertEqual (printf "UTF-16 encoded '\\x%X'" c)
609 (Right s) (eitherDecode $ utf16Char s)
611 utf16Char = formatString . LBase16.encode . LT.encodeUtf16BE
613 | L.length s == 4 = L.concat ["\"\\u", s, "\""]
615 L.concat ["\"\\u", L.take 4 s, "\\u", L.drop 4 s, "\""]
616 | otherwise = error "unescapeString: can't happen"
620jsonTestSuiteTest :: FilePath -> Test
621jsonTestSuiteTest path = testCase fileName $ do
622 payload <- L.readFile path
623 let result = eitherDecode payload :: Either String Value
624 assertBool fileName $ case take 2 fileName of
625 "i_" -> isRight result
626 "n_" -> isLeft result
627 "y_" -> isRight result
628 _ -> isRight result -- test_transform tests have inconsistent names
630 fileName = takeFileName path
632-- Build a collection of tests based on the current contents of the
633-- JSONTestSuite test directories.
635jsonTestSuite :: IO Test
637 let suitePath = "tests/JSONTestSuite"
638 exists <- doesDirectoryExist suitePath
640 hPutStrLn stderr $ "git clone https://github.com/nst/JSONTestSuite " ++
642 exitWith (ExitFailure 1)
643 -- ignore test_transform for now
644 let suites = ["test_parsing", "test_transform"]
645 testPaths <- fmap (sort . concat) . forM suites $ \suite -> do
646 let dir = suitePath </> suite
647 entries <- getDirectoryContents dir
648 let ok name = takeExtension name == ".json" &&
649 not (name `HashSet.member` blacklist)
650 return . map (dir </>) . filter ok $ entries
651 return $ testGroup "JSONTestSuite" $ map jsonTestSuiteTest testPaths
653-- The set expected-to-be-failing JSONTestSuite tests.
654-- Not all of these failures are genuine bugs.
655-- Of those that are bugs, not all are worth fixing.
657blacklist :: HashSet.HashSet String
658-- blacklist = HashSet.empty
659blacklist = _blacklist
661_blacklist :: HashSet.HashSet String
662_blacklist = HashSet.fromList [
663 "i_object_key_lone_2nd_surrogate.json"
664 , "i_string_1st_surrogate_but_2nd_missing.json"
665 , "i_string_1st_valid_surrogate_2nd_invalid.json"
666 , "i_string_UTF-16LE_with_BOM.json"
667 , "i_string_UTF-16_invalid_lonely_surrogate.json"
668 , "i_string_UTF-16_invalid_surrogate.json"
669 , "i_string_UTF-8_invalid_sequence.json"
670 , "i_string_incomplete_surrogate_and_escape_valid.json"
671 , "i_string_incomplete_surrogate_pair.json"
672 , "i_string_incomplete_surrogates_escape_valid.json"
673 , "i_string_invalid_lonely_surrogate.json"
674 , "i_string_invalid_surrogate.json"
675 , "i_string_inverted_surrogates_U+1D11E.json"
676 , "i_string_lone_second_surrogate.json"
677 , "i_string_not_in_unicode_range.json"
678 , "i_string_truncated-utf-8.json"
679 , "i_structure_UTF-8_BOM_empty_object.json"
680 , "n_string_unescaped_crtl_char.json"
681 , "n_string_unescaped_newline.json"
682 , "n_string_unescaped_tab.json"
683 , "string_1_escaped_invalid_codepoint.json"
684 , "string_1_invalid_codepoint.json"
685 , "string_1_invalid_codepoints.json"
686 , "string_2_escaped_invalid_codepoints.json"
687 , "string_2_invalid_codepoints.json"
688 , "string_3_escaped_invalid_codepoints.json"
689 , "string_3_invalid_codepoints.json"
690 , "y_string_utf16BE_no_BOM.json"
691 , "y_string_utf16LE_no_BOM.json"
694-- A regression test for: https://github.com/bos/aeson/pull/455
695data Foo a = FooNil | FooCons (Foo Int)
698pr455 = assertEqual "FooCons FooNil"
699 (toJSON foo) (liftToJSON undefined undefined foo)
704deriveJSON defaultOptions{omitNothingFields=True} ''MyRecord
706deriveToJSON defaultOptions ''Foo
707deriveToJSON1 defaultOptions ''Foo
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list