packages feed

dataframe-json (empty) → 1.0.0.0

raw patch · 3 files changed

+192/−0 lines, 3 filesdep +aesondep +basedep +bytestring

Dependencies added: aeson, base, bytestring, containers, dataframe-core, dataframe-operations, scientific, text, vector

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2026 Michael Chavinda++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ dataframe-json.cabal view
@@ -0,0 +1,39 @@+cabal-version:      2.4+name:               dataframe-json+version:            1.0.0.0++synopsis:           JSON reader and writer for the dataframe ecosystem.+description:+    @DataFrame.IO.JSON@ — read/write arrays of JSON objects as+    'DataFrame's. Built on @aeson@.++bug-reports:        https://github.com/mchav/dataframe/issues+license:            MIT+license-file:       LICENSE+author:             Michael Chavinda+maintainer:         mschavinda@gmail.com+copyright:          (c) 2024-2025 Michael Chavinda+category:           Data+tested-with:        GHC ==9.4.8 || ==9.6.7 || ==9.8.4 || ==9.10.3 || ==9.12.2++common warnings+    ghc-options:+        -Wincomplete-patterns+        -Wincomplete-uni-patterns+        -Wunused-imports+        -Wunused-local-binds++library+    import:             warnings+    exposed-modules:    DataFrame.IO.JSON+    build-depends:      base >= 4 && < 5,+                        aeson >= 0.11.0.0 && < 3,+                        bytestring >= 0.11 && < 0.13,+                        containers >= 0.6.7 && < 0.9,+                        dataframe-core ^>= 1.0,+                        dataframe-operations ^>= 1.0,+                        scientific >= 0.3.1 && < 0.4,+                        text >= 2.0 && < 3,+                        vector ^>= 0.13+    hs-source-dirs:     src+    default-language:   Haskell2010
+ src/DataFrame/IO/JSON.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE TypeApplications #-}++module DataFrame.IO.JSON (+    readJSON,+    readJSONEither,+) where++import Control.Monad (forM)+import Data.Aeson+import qualified Data.Aeson.Key as K+import qualified Data.Aeson.KeyMap as KM+import qualified Data.ByteString.Lazy as LBS+import Data.Maybe (catMaybes)+import Data.Scientific (toRealFloat)+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Vector as V++import qualified DataFrame.Internal.Column as D+import qualified DataFrame.Internal.DataFrame as D+import qualified DataFrame.Operations.Core as D++readJSONEither :: LBS.ByteString -> Either String D.DataFrame+readJSONEither bs = do+    v <- note "Could not decode JSON" (decode @Value bs)+    rows <- toArrayOfObjects v+    let cols :: [Text]+        cols =+            uniq+                . concatMap (map K.toText . KM.keys)+                . V.toList+                $ rows++    columns <- forM cols $ \c -> do+        let col = buildColumn rows c+        pure (c, col)++    pure $ D.fromNamedColumns columns++readJSON :: FilePath -> IO D.DataFrame+readJSON path = do+    contents <- LBS.readFile path+    case readJSONEither contents of+        Left err -> fail $ "readJSON: " <> err+        Right df -> pure df++toArrayOfObjects :: Value -> Either String (V.Vector Object)+toArrayOfObjects (Array xs)+    | V.null xs = Left "Top-level JSON array is empty"+    | otherwise = traverse asObject xs+toArrayOfObjects _ =+    Left "Top-level JSON value must be a JSON array of objects"++asObject :: Value -> Either String Object+asObject (Object o) = Right o+asObject _ = Left "Expected each element of the array to be an object"++uniq :: (Ord a) => [a] -> [a]+uniq = go mempty+  where+    go _ [] = []+    go seen (x : xs)+        | x `elem` seen = go seen xs+        | otherwise = x : go (x : seen) xs++note :: e -> Maybe a -> Either e a+note e = maybe (Left e) Right++data ColType+    = CTString+    | CTNumber+    | CTBool+    | CTArray+    | CTMixed++buildColumn :: V.Vector Object -> Text -> D.Column+buildColumn rows colName =+    let key = K.fromText colName+        values :: V.Vector (Maybe Value)+        values = V.map (KM.lookup key) rows+        colType = detectColType values+     in case colType of+            CTString ->+                D.fromVector (fmap (fmap asText) values)+            CTNumber ->+                D.fromVector (fmap (fmap asDouble) values)+            CTBool ->+                D.fromVector (fmap (fmap asBool) values)+            CTArray ->+                D.fromVector (fmap (fmap asArray) values)+            CTMixed ->+                D.fromVector values++detectColType :: V.Vector (Maybe Value) -> ColType+detectColType vals =+    case nonMissing of+        [] -> CTMixed+        vs+            | all isString vs -> CTString+            | all isNumber vs -> CTNumber+            | all isBool vs -> CTBool+            | all isArray vs -> CTArray+            | otherwise -> CTMixed+  where+    nonMissing = catMaybes (V.toList vals)++    isString (String _) = True+    isString _ = False++    isNumber (Number _) = True+    isNumber _ = False++    isBool (Bool _) = True+    isBool _ = False++    isArray (Array _) = True+    isArray _ = False++asText :: Value -> Text+asText (String s) = s+asText v = T.pack (show v)++asDouble :: Value -> Double+asDouble (Number s) = toRealFloat @Double s+asDouble v = error $ "asDouble: non-number value: " <> show v++asBool :: Value -> Bool+asBool (Bool b) = b+asBool v = error $ "asBool: non-bool value: " <> show v++asArray :: Value -> V.Vector Value+asArray (Array a) = a+asArray v = error $ "asArray: non-array value: " <> show v