hw-json 1.3.1.1 → 1.3.2.0
raw patch · 13 files changed
+207/−106 lines, 13 filesdep +doctestdep +doctest-discoverdep +unordered-containersdep −semigroupsdep ~basedep ~generic-lensdep ~hw-balancedparensPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: doctest, doctest-discover, unordered-containers
Dependencies removed: semigroups
Dependency ranges changed: base, generic-lens, hw-balancedparens
API changes (from Hackage documentation)
- HaskellWorks.Data.Json.LightJson: instance GHC.Classes.Eq (HaskellWorks.Data.Json.LightJson.LightJson c)
+ HaskellWorks.Data.Json.LightJson: instance (HaskellWorks.Data.BalancedParens.BalancedParens.BalancedParens w, HaskellWorks.Data.RankSelect.Base.Rank0.Rank0 w, HaskellWorks.Data.RankSelect.Base.Rank1.Rank1 w, HaskellWorks.Data.RankSelect.Base.Select1.Select1 v, HaskellWorks.Data.Bits.BitWise.TestBit w) => HaskellWorks.Data.Json.LightJson.LightJsonAt (HaskellWorks.Data.Json.Simple.Cursor.JsonCursor Data.ByteString.Internal.ByteString v w)
+ HaskellWorks.Data.Json.LightJson: instance HaskellWorks.Data.Json.LightJson.LightJsonAt c => GHC.Classes.Eq (HaskellWorks.Data.Json.LightJson.LightJson c)
Files
- README.md +24/−48
- app/App/Commands.hs +2/−1
- app/App/Commands/Count.hs +1/−2
- app/App/Commands/CountAeson.hs +52/−0
- app/App/Commands/CreateIndex.hs +0/−1
- app/App/Commands/Demo.hs +0/−1
- app/App/Commands/Types.hs +7/−1
- app/Main.hs +0/−1
- doctest/DoctestDriver.hs +12/−0
- hw-json.cabal +60/−46
- src/HaskellWorks/Data/Json/Internal/Slurp.hs +18/−0
- src/HaskellWorks/Data/Json/LightJson.hs +28/−5
- test/HaskellWorks/Data/Json/Simple/CursorSpec.hs +3/−0
README.md view
@@ -1,4 +1,5 @@ # hw-json+ [](https://circleci.com/gh/haskell-works/hw-json/tree/master) `hw-json` is a succinct JSON parsing library.@@ -34,12 +35,11 @@ ```haskell -- CMD -- Mem (MB) ---------------------------------------------------------- -- ---------import Control.DeepSeq -- 94-import Data.Aeson -- 100-import qualified Data.ByteString.Lazy as BSL -- 104-bs <- BSL.readFile "../corpus/bench/hospitalisation.json" -- 105-let !x = deepseq bs bs -- 146-let !y = decode json78m :: Maybe Value -- 669+import Control.DeepSeq -- 345+import Data.Aeson -- 371+import qualified Data.ByteString.Lazy as LBS -- 376+!bs <- LBS.readFile "corpus/bench/hospitalisation.json" -- 380+let !y = decode bs :: Maybe Value -- 928 ``` ### Parsing large Json files in Haskell with hw-json@@ -47,20 +47,12 @@ ```haskell -- CMD -- Mem (MB) --------------------------------------------------------------------- -- ---------import Foreign -- 93-import Control.Monad -- 95-import Data.Word -- 96-import HaskellWorks.Data.BalancedParens.Simple -- 97-import HaskellWorks.Data.Bits.BitShown -- 98-import HaskellWorks.Data.FromForeignRegion -- 99-import HaskellWorks.Data.Json.Backend.Standard.Cursor -- 106-import System.IO.MMap -- 109-import qualified Data.ByteString as BS -- 110-import qualified Data.Vector.Storable as DVS -- 111-import qualified HaskellWorks.Data.ByteString as BS -- 112-import qualified HaskellWorks.Data.Json.Backend.Standard.Fast as FAST -- 114-bs <- BS.mmap "../corpus/bench/hospitalisation.json" -- 115-let !cursor = FAST.makeCursor bs -- 203+import qualified HaskellWorks.Data.ByteString as BS -- 351+import qualified HaskellWorks.Data.Json.Standard.Cursor.Fast as JCF -- 353++!jsonBs <- BS.mmap "corpus/bench/hospitalisation.json" -- 355+let !ibip = JCF.simdToIbBp jsonBs -- 358+let !c = JCF.fromBsIbBp jsonBs ibip -- 495 ``` ## Examples@@ -69,32 +61,18 @@ ```haskell import Control.Monad-import Data.String-import Data.Word-import HaskellWorks.Data.BalancedParens.Simple-import HaskellWorks.Data.Bits.BitShow-import HaskellWorks.Data.Bits.BitShown-import HaskellWorks.Data.FromForeignRegion-import HaskellWorks.Data.Json.Backend.Standard.Cursor-import HaskellWorks.Data.Json.Internal.Token.Types-import HaskellWorks.Data.RankSelect.Base.Rank0-import HaskellWorks.Data.RankSelect.Base.Rank1-import HaskellWorks.Data.RankSelect.Base.Select1-import HaskellWorks.Data.RankSelect.CsPoppy-import System.IO.MMap -import qualified Data.ByteString as BS-import qualified Data.Vector.Storable as DVS-import qualified HaskellWorks.Data.Json.Backend.Standard.Cursor as C-import qualified HaskellWorks.Data.Json.Backend.Standard.Fast as FAST-import qualified HaskellWorks.Data.TreeCursor as TC+import qualified Data.ByteString as BS+import qualified HaskellWorks.Data.Json.Standard.Cursor.Fast as JCF+import qualified HaskellWorks.Data.TreeCursor as TC let fc = TC.firstChild let ns = TC.nextSibling let pn = TC.parent let ss = TC.subtreeSize-let cursor = FAST.makeCursor "[null, {\"field\": 1}]"-cursor+let jsonBs = "[null, {\"field\": 1}]" :: BS.ByteString+let ibip = JCF.simdToIbBp jsonBs+let cursor = JCF.fromBsIbBp jsonBs ibip fc cursor (fc >=> ns) cursor ```@@ -105,20 +83,18 @@ import Control.Monad import Data.Function import Data.List-import HaskellWorks.Data.Json.Backend.Standard.Load.Cursor-import HaskellWorks.Data.Json.Backend.Standard.Load.Partial-import HaskellWorks.Data.Json.Backend.Standard.Load.Raw import HaskellWorks.Data.Json.PartialValue+import HaskellWorks.Data.Json.Standard.Cursor.Load.Cursor+import HaskellWorks.Data.Json.Standard.Load.Partial import HaskellWorks.Data.MQuery import HaskellWorks.Data.MQuery.Micro-import HaskellWorks.Data.MQuery.Row import qualified Data.DList as DL -!cursor <- loadPartial "../corpus/bench/78mb.json"-!cursor <- loadCursorWithIndex "../corpus/bench/78mb.json"-!cursor <- loadCursor "../corpus/bench/78mb.json"-!cursor <- loadCursorWithCsPoppyIndex "../corpus/bench/78mb.json"+!cursor <- loadPartial "corpus/bench/78mb.json"+!cursor <- loadCursorWithIndex "corpus/bench/78mb.json"+!cursor <- loadCursor "corpus/bench/78mb.json"+!cursor <- loadCursorWithCsPoppyIndex "corpus/bench/78mb.json" let !json = jsonPartialJsonValueAt cursor let q = MQuery (DL.singleton json)
app/App/Commands.hs view
@@ -1,9 +1,9 @@ module App.Commands where import App.Commands.Count+import App.Commands.CountAeson import App.Commands.CreateIndex import App.Commands.Demo-import Data.Semigroup ((<>)) import Options.Applicative commands :: Parser (IO ())@@ -14,4 +14,5 @@ <> commandGroup "Commands:" <> cmdCreateIndex <> cmdCount+ <> cmdCountAeson <> cmdDemo
app/App/Commands/Count.hs view
@@ -11,7 +11,6 @@ import Control.Lens import Control.Monad import Data.Generics.Product.Any-import Data.Semigroup ((<>)) import Data.Word import HaskellWorks.Data.FromForeignRegion import HaskellWorks.Data.Json.LightJson@@ -88,7 +87,7 @@ <> metavar "FILE" ) <*> optional optsFileIndex- <*> option auto+ <*> strOption ( long "expression" <> help "JSON expression" <> metavar "EXPRESSION"
+ app/App/Commands/CountAeson.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module App.Commands.CountAeson+ ( cmdCountAeson+ ) where++import Control.Lens+import Data.Generics.Product.Any+import Options.Applicative hiding (columns)++import qualified App.Commands.Types as Z+import qualified Data.Aeson as J+import qualified Data.ByteString.Lazy as LBS+import qualified Data.HashMap.Strict as SHM+import qualified System.IO as IO++runCountAeson :: Z.CountAesonOptions -> IO ()+runCountAeson opts = do+ let inputFile = opts ^. the @"inputFile"+ let expression = opts ^. the @"expression"++ lbs <- LBS.readFile inputFile++ let lbsLines = LBS.split 10 lbs++ let count :: Int = sum $ flip fmap lbsLines $ \lbsLine -> case J.decode lbsLine of+ Just (J.Object v) -> if SHM.member expression v then 1 else 0+ _ -> 0++ IO.putStrLn $ "Count: " <> show count++ return ()++optsCountAeson :: Parser Z.CountAesonOptions+optsCountAeson = Z.CountAesonOptions+ <$> strOption+ ( long "input"+ <> short 'i'+ <> help "Input JSON file"+ <> metavar "FILE"+ )+ <*> strOption+ ( long "expression"+ <> help "JSON expression"+ <> metavar "EXPRESSION"+ )++cmdCountAeson :: Mod CommandFields (IO ())+cmdCountAeson = command "count-aeson" $ flip info idm $ runCountAeson <$> optsCountAeson
app/App/Commands/CreateIndex.hs view
@@ -11,7 +11,6 @@ import Control.Monad import Data.Generics.Product.Any import Data.Maybe-import Data.Semigroup ((<>)) import Data.Word import Foreign import Options.Applicative hiding (columns)
app/App/Commands/Demo.hs view
@@ -11,7 +11,6 @@ import Control.Lens import Control.Monad import Data.Generics.Product.Any-import Data.Semigroup ((<>)) import Data.Word import Foreign.ForeignPtr import HaskellWorks.Data.Json.LightJson
app/App/Commands/Types.hs view
@@ -2,7 +2,8 @@ {-# LANGUAGE DuplicateRecordFields #-} module App.Commands.Types- ( CountOptions(..)+ ( CountAesonOptions(..)+ , CountOptions(..) , CreateIndexOptions(..) , DemoOptions(..) , FileIndexes(..)@@ -27,6 +28,11 @@ data CountOptions = CountOptions { inputFile :: FilePath , indexes :: Maybe FileIndexes+ , expression :: Text+ } deriving (Eq, Show, Generic)++data CountAesonOptions = CountAesonOptions+ { inputFile :: FilePath , expression :: Text } deriving (Eq, Show, Generic)
app/Main.hs view
@@ -2,7 +2,6 @@ import App.Commands import Control.Monad-import Data.Semigroup ((<>)) import Options.Applicative main :: IO ()
+ doctest/DoctestDriver.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE CPP #-}++#if MIN_VERSION_GLASGOW_HASKELL(8,4,4,0)+{-# OPTIONS_GHC -F -pgmF doctest-discover #-}+#else+module Main where++import qualified System.IO as IO++main :: IO ()+main = IO.putStrLn "WARNING: doctest will not run on GHC versions earlier than 8.4.4"+#endif
hw-json.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2 name: hw-json-version: 1.3.1.1+version: 1.3.2.0 synopsis: Memory efficient JSON parser description: Memory efficient JSON parser. Please see README.md category: Data@@ -9,10 +9,10 @@ bug-reports: https://github.com/haskell-works/hw-json/issues author: John Ky maintainer: newhoggy@gmail.com-copyright: 2016-2019 John Ky+copyright: 2016-2020 John Ky license: BSD-3-Clause license-file: LICENSE-tested-with: GHC == 8.8.1, GHC == 8.6.5, GHC == 8.4.4, GHC == 8.2.2+tested-with: GHC == 8.10.1, GHC == 8.8.3, GHC == 8.6.5, GHC == 8.4.4 build-type: Simple extra-source-files: README.md corpus/5000B.json@@ -37,42 +37,43 @@ manual: False default: False -common base { build-depends: base >= 4 && < 5 }--common aeson { build-depends: aeson >= 1.4.3.0 && < 1.5 }-common ansi-wl-pprint { build-depends: ansi-wl-pprint >= 0.6.8.2 && < 0.7 }-common array { build-depends: array >= 0.5 && < 0.6 }-common attoparsec { build-depends: attoparsec >= 0.13 && < 0.14 }-common bits-extra { build-depends: bits-extra >= 0.0.1.3 && < 0.1 }-common bytestring { build-depends: bytestring >= 0.10.6 && < 0.11 }-common criterion { build-depends: criterion >= 1.4 && < 1.6 }-common directory { build-depends: directory >= 1.3 && < 1.4 }-common dlist { build-depends: dlist >= 0.8 && < 0.9 }-common generic-lens { build-depends: generic-lens >= 1.2.0.1 && < 1.3 }-common hedgehog { build-depends: hedgehog >= 0.6 && < 1.1 }-common hspec { build-depends: hspec >= 2.4 && < 3 }-common hw-balancedparens { build-depends: hw-balancedparens >= 0.3.0.0 && < 0.4 }-common hw-bits { build-depends: hw-bits >= 0.7.0.5 && < 0.8 }-common hw-hspec-hedgehog { build-depends: hw-hspec-hedgehog >= 0.1.0.4 && < 0.2 }-common hw-json-simd { build-depends: hw-json-simd >= 0.1.0.2 && < 0.2 }-common hw-json-simple-cursor { build-depends: hw-json-simple-cursor >= 0.1.0.1 && < 0.2 }-common hw-json-standard-cursor { build-depends: hw-json-standard-cursor >= 0.2.0.1 && < 0.3 }-common hw-mquery { build-depends: hw-mquery >= 0.2.0.0 && < 0.3 }-common hw-parser { build-depends: hw-parser >= 0.1 && < 0.2 }-common hw-prim { build-depends: hw-prim >= 0.6.2.32 && < 0.7 }-common hw-rankselect { build-depends: hw-rankselect >= 0.13 && < 0.14 }-common hw-rankselect-base { build-depends: hw-rankselect-base >= 0.3.2.1 && < 0.4 }-common hw-simd { build-depends: hw-simd >= 0.1.1.2 && < 0.2 }-common lens { build-depends: lens >= 4 && < 5 }-common mmap { build-depends: mmap >= 0.5 && < 0.6 }-common optparse-applicative { build-depends: optparse-applicative >= 0.14 && < 0.16 }-common scientific { build-depends: scientific >= 0.3.6.2 && < 0.4 }-common text { build-depends: text >= 1.2 && < 1.3 }-common transformers { build-depends: transformers >= 0.4 && < 0.6 }-common vector { build-depends: vector >= 0.12 && < 0.13 }-common word8 { build-depends: word8 >= 0.1 && < 0.2 }+common base { build-depends: base >= 4.11 && < 5 } -common semigroups { if impl(ghc < 8 ) { build-depends: semigroups >= 0.16 && < 0.19 } }+common aeson { build-depends: aeson >= 1.4.3.0 && < 1.5 }+common ansi-wl-pprint { build-depends: ansi-wl-pprint >= 0.6.8.2 && < 0.7 }+common array { build-depends: array >= 0.5 && < 0.6 }+common attoparsec { build-depends: attoparsec >= 0.13 && < 0.14 }+common bits-extra { build-depends: bits-extra >= 0.0.1.3 && < 0.1 }+common bytestring { build-depends: bytestring >= 0.10.6 && < 0.11 }+common criterion { build-depends: criterion >= 1.4 && < 1.6 }+common directory { build-depends: directory >= 1.3 && < 1.4 }+common dlist { build-depends: dlist >= 0.8 && < 0.9 }+common doctest { build-depends: doctest >= 0.16.2 && < 0.17 }+common doctest-discover { build-depends: doctest-discover >= 0.2 && < 0.3 }+common generic-lens { build-depends: generic-lens >= 1.2.0.1 && < 1.3 }+common hedgehog { build-depends: hedgehog >= 0.6 && < 1.1 }+common hspec { build-depends: hspec >= 2.4 && < 3 }+common hw-balancedparens { build-depends: hw-balancedparens >= 0.3.0.0 && < 0.4 }+common hw-bits { build-depends: hw-bits >= 0.7.0.5 && < 0.8 }+common hw-hspec-hedgehog { build-depends: hw-hspec-hedgehog >= 0.1.0.4 && < 0.2 }+common hw-json-simd { build-depends: hw-json-simd >= 0.1.0.2 && < 0.2 }+common hw-json-simple-cursor { build-depends: hw-json-simple-cursor >= 0.1.0.1 && < 0.2 }+common hw-json-standard-cursor { build-depends: hw-json-standard-cursor >= 0.2.0.1 && < 0.3 }+common hw-mquery { build-depends: hw-mquery >= 0.2.0.0 && < 0.3 }+common hw-parser { build-depends: hw-parser >= 0.1 && < 0.2 }+common hw-prim { build-depends: hw-prim >= 0.6.2.32 && < 0.7 }+common hw-rankselect { build-depends: hw-rankselect >= 0.13 && < 0.14 }+common hw-rankselect-base { build-depends: hw-rankselect-base >= 0.3.2.1 && < 0.4 }+common hw-simd { build-depends: hw-simd >= 0.1.1.2 && < 0.2 }+common lens { build-depends: lens >= 4 && < 5 }+common mmap { build-depends: mmap >= 0.5 && < 0.6 }+common optparse-applicative { build-depends: optparse-applicative >= 0.14 && < 0.16 }+common scientific { build-depends: scientific >= 0.3.6.2 && < 0.4 }+common text { build-depends: text >= 1.2 && < 1.3 }+common transformers { build-depends: transformers >= 0.4 && < 0.6 }+common unordered-containers { build-depends: unordered-containers >= 0.2 && < 0.3 }+common vector { build-depends: vector >= 0.12 && < 0.13 }+common word8 { build-depends: word8 >= 0.1 && < 0.2 } common config default-language: Haskell2010@@ -83,6 +84,9 @@ ghc-options: -mbmi2 -msse4.2 cpp-options: -DBMI2_ENABLED +common hw-json+ build-depends: hw-json+ library import: base, config , aeson@@ -102,7 +106,6 @@ , hw-rankselect-base , hw-simd , mmap- , semigroups , text , vector , word8@@ -132,10 +135,12 @@ executable hw-json import: base, config+ , aeson , bytestring , dlist , generic-lens , hw-balancedparens+ , hw-json , hw-json-simd , hw-json-simple-cursor , hw-json-standard-cursor@@ -146,16 +151,16 @@ , lens , mmap , optparse-applicative- , semigroups , text+ , unordered-containers , vector main-is: Main.hs hs-source-dirs: app ghc-options: -threaded -rtsopts -with-rtsopts=-N- build-depends: hw-json other-modules: App.Commands App.Commands.CreateIndex App.Commands.Count+ App.Commands.CountAeson App.Commands.Demo App.Commands.Types @@ -169,6 +174,7 @@ , hw-balancedparens , hw-bits , hw-hspec-hedgehog+ , hw-json , hw-json-simple-cursor , hw-json-standard-cursor , hw-prim@@ -180,7 +186,6 @@ , vector type: exitcode-stdio-1.0 main-is: Spec.hs- build-depends: hw-json hs-source-dirs: test ghc-options: -threaded -rtsopts -with-rtsopts=-N build-tool-depends: hspec-discover:hspec-discover@@ -191,18 +196,27 @@ HaskellWorks.Data.Json.Token.TokenizeSpec HaskellWorks.Data.Json.TypeSpec HaskellWorks.Data.Json.ValueSpec- Paths_hw_json benchmark bench import: base, config , bytestring , criterion , directory+ , hw-json , hw-json-standard-cursor , mmap- , semigroups type: exitcode-stdio-1.0 main-is: Main.hs hs-source-dirs: bench- build-depends: hw-json- other-modules: Paths_hw_json++test-suite hw-json-doctest+ import: base, config+ , doctest+ , doctest-discover+ , hw-json+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ ghc-options: -threaded+ main-is: DoctestDriver.hs+ HS-Source-Dirs: doctest+ build-tool-depends: doctest-discover:doctest-discover
src/HaskellWorks/Data/Json/Internal/Slurp.hs view
@@ -24,11 +24,29 @@ | InNumber | InIdent +-- | Slurp a JSON string+--+-- Examples:+--+-- >>> :set -XOverloadedStrings+-- >>> slurpText "\"Hello\""+-- Right "Hello"+-- >>> slurpText "123"+-- Left "34: Failed reading: satisfy" slurpText :: BS.ByteString -> Either Text Text slurpText bs = case PBS.parseOnly AP.jstring bs of Right t -> Right t Left e -> Left (T.pack e) +-- | Slurp a JSON number+--+-- Examples:+--+-- >>> :set -XOverloadedStrings+-- >>> slurpNumber "123, true"+-- "123"+-- >>> slurpNumber "\"Hello\""+-- "\"Hello\"" slurpNumber :: BS.ByteString -> BS.ByteString slurpNumber bs = let (!cs, _) = BS.unfoldrN (BS.length bs) genNumber (InJson, bs) in cs where genNumber :: (JsonState, BS.ByteString) -> Maybe (Word8, (JsonState, BS.ByteString))
src/HaskellWorks/Data/Json/LightJson.hs view
@@ -33,10 +33,12 @@ import Prelude hiding (drop) import Text.PrettyPrint.ANSI.Leijen -import qualified Data.ByteString as BS-import qualified Data.List as L-import qualified Data.Text as T-import qualified HaskellWorks.Data.BalancedParens as BP+import qualified Data.ByteString as BS+import qualified Data.ByteString.Unsafe as BSU+import qualified Data.List as L+import qualified Data.Text as T+import qualified HaskellWorks.Data.BalancedParens as BP+import qualified HaskellWorks.Data.Json.Simple.Cursor as JSC data LightJson c = LightJsonString Text@@ -48,11 +50,14 @@ | LightJsonError Text deriving Show -instance Eq (LightJson c) where+instance LightJsonAt c => Eq (LightJson c) where (==) (LightJsonString a) (LightJsonString b) = a == b (==) (LightJsonNumber a) (LightJsonNumber b) = a == b (==) (LightJsonBool a) (LightJsonBool b) = a == b+ (==) (LightJsonObject a) (LightJsonObject b) = fmap (fmap lightJsonAt) a == fmap (fmap lightJsonAt) b+ (==) (LightJsonArray a) (LightJsonArray b) = fmap lightJsonAt a == fmap lightJsonAt b (==) LightJsonNull LightJsonNull = True+ (==) (LightJsonError a) (LightJsonError b) = a == b (==) _ _ = False data LightJsonField c = LightJsonField Text (LightJson c)@@ -144,3 +149,21 @@ asField (a, b) = case lightJsonAt a of LightJsonString s -> [(s, b)] _ -> []++instance (BP.BalancedParens w, Rank0 w, Rank1 w, Select1 v, TestBit w) => LightJsonAt (JSC.JsonCursor BS.ByteString v w) where+ lightJsonAt k = if kra `mod` 2 == 1+ then let i = fromIntegral (kpa - 1) :: Int in+ if i < BS.length kt+ then case BSU.unsafeIndex kt i of+ 91 -> LightJsonArray []+ 123 -> LightJsonObject []+ _ -> LightJsonError "Invalid collection character"+ else LightJsonError "Index out of bounds"+ else LightJsonError "Unaligned cursor"+ where kpa = select1 kib kta + km+ kib = JSC.interests k+ kra = JSC.cursorRank k+ ksa = kra + 1+ kta = ksa `div` 2+ km = ksa `mod` 2+ kt = JSC.cursorText k
test/HaskellWorks/Data/Json/Simple/CursorSpec.hs view
@@ -14,6 +14,7 @@ ) where import Control.Monad+import HaskellWorks.Data.Json.LightJson import HaskellWorks.Hspec.Hedgehog import Hedgehog import Test.Hspec@@ -82,3 +83,5 @@ (V.snippet <$> (fc >=> fc >=> fc ) k) === Just "11" (V.snippet <$> (fc >=> fc >=> fc >=> ns ) k) === Nothing (V.snippet <$> (fc >=> fc >=> fc >=> fc ) k) === Nothing+ it "can lightJsonAt" $ requireTest $ do+ lightJsonAt k === LightJsonArray []