packages feed

dataframe-0.7.0.0: tests/IO/JSON.hs

{-# LANGUAGE OverloadedStrings #-}

module IO.JSON where

import qualified Data.ByteString.Lazy.Char8 as LBSC
import DataFrame.IO.JSON (readJSONEither)
import qualified DataFrame.Internal.Column as DI
import qualified DataFrame.Internal.DataFrame as DI
import qualified DataFrame.Operations.Core as D
import Test.HUnit (
    Test (TestCase, TestLabel),
    assertBool,
    assertEqual,
    assertFailure,
 )

-- | Happy path: array of objects with string and number columns.
jsonHappyPath :: Test
jsonHappyPath =
    TestCase
        ( case readJSONEither
            (LBSC.pack "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]") of
            Left err -> assertFailure $ "Unexpected Left: " ++ err
            Right df -> do
                assertEqual "Happy path: 2 rows" 2 (D.nRows df)
                assertEqual "Happy path: 2 columns" 2 (D.nColumns df)
        )

-- | Boolean column is preserved correctly.
jsonBoolColumn :: Test
jsonBoolColumn =
    TestCase
        ( case readJSONEither (LBSC.pack "[{\"flag\":true},{\"flag\":false}]") of
            Left err -> assertFailure $ "Unexpected Left: " ++ err
            Right df -> do
                assertEqual "Bool column: 2 rows" 2 (D.nRows df)
                assertEqual "Bool column: 1 column" 1 (D.nColumns df)
        )

-- | A key absent from some objects produces an Optional column.
jsonMissingKeyBecomesOptional :: Test
jsonMissingKeyBecomesOptional =
    TestCase
        ( case readJSONEither (LBSC.pack "[{\"a\":1,\"b\":2},{\"a\":3}]") of
            Left err -> assertFailure $ "Unexpected Left: " ++ err
            Right df -> do
                assertEqual "Missing key: 2 rows" 2 (D.nRows df)
                assertEqual "Missing key: 2 columns" 2 (D.nColumns df)
                -- 'b' is absent from the second row, so must have a missing value
                assertBool
                    "b column should have missing values"
                    (maybe False DI.hasMissing (DI.getColumn "b" df))
        )

-- | When column values are different types across rows, the column becomes generic.
jsonMixedTypeColumn :: Test
jsonMixedTypeColumn =
    TestCase
        ( case readJSONEither (LBSC.pack "[{\"x\":1},{\"x\":\"hello\"}]") of
            Left err -> assertFailure $ "Unexpected Left: " ++ err
            Right df -> do
                assertEqual "Mixed type: 2 rows" 2 (D.nRows df)
                assertEqual "Mixed type: 1 column" 1 (D.nColumns df)
        )

-- | An empty top-level JSON array is rejected.
jsonEmptyArray :: Test
jsonEmptyArray =
    TestCase
        ( case readJSONEither (LBSC.pack "[]") of
            Left _ -> return ()
            Right _ -> assertFailure "Expected Left for empty array"
        )

-- | A non-array top-level JSON value is rejected.
jsonNonArray :: Test
jsonNonArray =
    TestCase
        ( case readJSONEither (LBSC.pack "{\"name\":\"Alice\"}") of
            Left _ -> return ()
            Right _ -> assertFailure "Expected Left for non-array top-level value"
        )

-- | Array elements that are not objects are rejected.
jsonNonObjectElement :: Test
jsonNonObjectElement =
    TestCase
        ( case readJSONEither (LBSC.pack "[1, 2, 3]") of
            Left _ -> return ()
            Right _ -> assertFailure "Expected Left for non-object array elements"
        )

-- | Completely invalid JSON is rejected.
jsonInvalidJSON :: Test
jsonInvalidJSON =
    TestCase
        ( case readJSONEither (LBSC.pack "not valid json at all") of
            Left _ -> return ()
            Right _ -> assertFailure "Expected Left for invalid JSON"
        )

tests :: [Test]
tests =
    [ TestLabel "jsonHappyPath" jsonHappyPath
    , TestLabel "jsonBoolColumn" jsonBoolColumn
    , TestLabel "jsonMissingKeyBecomesOptional" jsonMissingKeyBecomesOptional
    , TestLabel "jsonMixedTypeColumn" jsonMixedTypeColumn
    , TestLabel "jsonEmptyArray" jsonEmptyArray
    , TestLabel "jsonNonArray" jsonNonArray
    , TestLabel "jsonNonObjectElement" jsonNonObjectElement
    , TestLabel "jsonInvalidJSON" jsonInvalidJSON
    ]