jsontsv 0.1.4.5 → 0.1.4.6
raw patch · 3 files changed
+92/−57 lines, 3 files
Files
- Main.hs +81/−52
- README.md +10/−4
- jsontsv.cabal +1/−1
Main.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings, QuasiQuotes, RecordWildCards #-} module Main where import Data.Aeson import Data.Monoid@@ -30,9 +30,12 @@ data Options = Options { jsonExpr :: String- , arrayDelim :: String+ , optArrayDelim :: Text , outputMode :: OutputMode , showHeader :: Bool+ , nullString :: Text+ , optTrueString :: Text+ , optFalseString :: Text , debugKeyPaths :: Bool } deriving Show @@ -41,9 +44,22 @@ parseOpts :: O.Parser Options parseOpts = Options <$> O.argument O.str (O.metavar "FIELDS")- <*> O.strOption (O.metavar "DELIM" <> O.value "," <> O.short 'a' <> O.help "Concatentated array elem delimiter. Defaults to comma.")+ <*> (T.pack + <$> O.strOption (O.metavar "DELIM" <> O.value "," <> O.short 'a' <> O.help "Concatentated array elem delimiter. Defaults to comma.")) <*> (parseCSVMode <|> parseTSVMode) <*> O.flag False True (O.short 'H' <> O.help "Include headers")+ <*> (T.pack + <$> O.strOption (O.value "null" + <> O.short 'n' <> O.long "null-string"+ <> O.metavar "STRING" <> O.help "String to represent null value. Default: 'null'"))+ <*> (T.pack + <$> O.strOption (O.value "t" + <> O.short 't' <> O.long "true-string"+ <> O.metavar "STRING" <> O.help "String to represent boolean true. Default: 't'"))+ <*> (T.pack + <$> O.strOption (O.value "f" + <> O.short 'f' <> O.long "false-string"+ <> O.metavar "STRING" <> O.help "String to represent boolean false. Default: 'f'")) <*> O.switch (O.long "debug" <> O.help "Debug keypaths") parseCSVMode = O.flag' CSVOutput (O.short 'c' <> O.long "csv" <> O.help "Output CSV")@@ -60,27 +76,33 @@ <> O.footer "See https://github.com/danchoi/jsontsv for more information.") main = do- Options expr arrayDelim mode showHeaders debugKeyPaths <- O.execParser opts+ Options{..} <- O.execParser opts x <- BL.getContents let xs :: [Value] xs = decodeStream x- ks = parseKeyPath $ T.pack expr+ ks = parseKeyPath $ T.pack jsonExpr -- keypaths without alias info: ks' = [ks' | KeyPath ks' _ <- ks]- arrayDelim' = T.pack arrayDelim when debugKeyPaths $ Prelude.putStrLn $ "key Paths " ++ show ks- when showHeaders $ do+ when showHeader $ do let hs = [case alias of Just alias' -> T.unpack alias'- Nothing -> expr - | (KeyPath _ alias, expr) <- Data.List.zip ks (words expr)] - case mode of + Nothing -> jsonExpr + | (KeyPath _ alias, jsonExpr) <- Data.List.zip ks (words jsonExpr)] + -- Note `words` introduces a potential bug is quoted aliases are allowed+ -- See https://github.com/danchoi/jsonxlsx/commit/9aedb4bf97cfa8d5635edc4780bfbf9b79b6f2ec+ case outputMode of TSVOutput delim -> Prelude.putStrLn . Data.List.intercalate delim $ hs CSVOutput -> Prelude.putStrLn . CSV.printCSV $ [hs]- case mode of - TSVOutput delim -> mapM_ (TL.putStrLn . B.toLazyText . evalToLineBuilder arrayDelim' delim ks') xs- CSVOutput -> Prelude.putStrLn . CSV.printCSV $ map (map T.unpack . evalToList arrayDelim' ks') $ xs+ let config = Config { + arrayDelim = optArrayDelim+ , nullValueString = nullString+ , trueString = optTrueString+ , falseString = optFalseString}+ case outputMode of + TSVOutput delim -> mapM_ (TL.putStrLn . B.toLazyText . evalToLineBuilder config delim ks') xs+ CSVOutput -> Prelude.putStrLn . CSV.printCSV $ map (map T.unpack . evalToList config ks') $ xs decodeStream :: (FromJSON a) => BL.ByteString -> [a] decodeStream bs = case decodeWith json bs of@@ -98,6 +120,13 @@ _ -> (Nothing, r)) $ fromJSON v' +data Config = Config {+ arrayDelim :: Text+ , nullValueString :: Text+ , trueString :: Text+ , falseString :: Text+ } deriving Show+ -- | KeyPath may have an alias for the header output data KeyPath = KeyPath [Key] (Maybe Text) deriving Show @@ -131,69 +160,69 @@ pIndex = Index <$> AT.decimal <* AT.char ']' -evalToLineBuilder :: Text -> String -> [[Key]] -> Value -> B.Builder -evalToLineBuilder arrayDelim delim ks v = - mconcat $ intersperse (B.fromText . T.pack $ delim) $ map (flip (evalToBuilder arrayDelim) v) ks+evalToLineBuilder :: Config -> String -> [[Key]] -> Value -> B.Builder +evalToLineBuilder config@Config{..} outDelim ks v = + mconcat $ intersperse (B.fromText . T.pack $ outDelim) $ map (flip (evalToBuilder config) v) ks type ArrayDelimiter = Text -evalToList :: Text -> [[Key]] -> Value -> [Text]-evalToList arrayDelim ks v = map (flip (evalToText arrayDelim) v) ks+evalToList :: Config -> [[Key]] -> Value -> [Text]+evalToList c@Config{..} ks v = map (flip (evalToText c) v) ks -evalToBuilder :: ArrayDelimiter -> [Key] -> Value -> B.Builder-evalToBuilder d k v = valToBuilder $ evalKeyPath d k v+evalToBuilder :: Config -> [Key] -> Value -> B.Builder+evalToBuilder c k v = valToBuilder c $ evalKeyPath c k v -evalToText :: ArrayDelimiter -> [Key] -> Value -> Text-evalToText d k v = valToText $ evalKeyPath d k v+evalToText :: Config -> [Key] -> Value -> Text+evalToText c k v = valToText c $ evalKeyPath c k v -- evaluates the a JS key path against a Value context to a leaf Value-evalKeyPath :: ArrayDelimiter -> [Key] -> Value -> Value-evalKeyPath d [] x@(String _) = x-evalKeyPath d [] x@Null = x-evalKeyPath d [] x@(Number _) = x-evalKeyPath d [] x@(Bool _) = x-evalKeyPath d [] x@(Object _) = x-evalKeyPath d [] x@(Array v) | V.null v = Null-evalKeyPath d [] x@(Array v) = +evalKeyPath :: Config -> [Key] -> Value -> Value+evalKeyPath config [] x@(String _) = x+evalKeyPath config [] x@Null = x+evalKeyPath config [] x@(Number _) = x+evalKeyPath config [] x@(Bool _) = x+evalKeyPath config [] x@(Object _) = x+evalKeyPath config [] x@(Array v) | V.null v = Null+evalKeyPath config [] x@(Array v) = let vs = V.toList v- xs = intersperse d $ map (evalToText d []) vs+ xs = intersperse (arrayDelim config) $ map (evalToText config []) vs in String . mconcat $ xs-evalKeyPath d (Key key:ks) (Object s) = +evalKeyPath config (Key key:ks) (Object s) = case (HM.lookup key s) of- Just x -> evalKeyPath d ks x+ Just x -> evalKeyPath config ks x Nothing -> Null-evalKeyPath d (Index idx:ks) (Array v) = +evalKeyPath config (Index idx:ks) (Array v) = let e = (V.!?) v idx in case e of - Just e' -> evalKeyPath d ks e'+ Just e' -> evalKeyPath config ks e' Nothing -> Null -- traverse array elements with additional keys-evalKeyPath d ks@(Key key:_) (Array v) | V.null v = Null-evalKeyPath d ks@(Key key:_) (Array v) = +evalKeyPath _ ks@(Key key:_) (Array v) | V.null v = Null+evalKeyPath config@Config{..} ks@(Key key:_) (Array v) = let vs = V.toList v- in String . mconcat . intersperse d $ map (evalToText d ks) vs+ in String . mconcat . intersperse arrayDelim $ map (evalToText config ks) vs evalKeyPath _ ((Index _):_) _ = Null evalKeyPath _ _ _ = Null -valToBuilder :: Value -> B.Builder-valToBuilder (String x) = B.fromText x-valToBuilder Null = B.fromText "null"-valToBuilder (Bool True) = B.fromText "t"-valToBuilder (Bool False) = B.fromText "f"-valToBuilder (Number x) = +valToBuilder :: Config -> Value -> B.Builder+valToBuilder _ (String x) = B.fromText x+valToBuilder Config{..} Null = B.fromText nullValueString+valToBuilder Config{..} (Bool True) = B.fromText trueString+valToBuilder Config{..} (Bool False) = B.fromText falseString+valToBuilder _ (Number x) = case floatingOrInteger x of Left float -> B.realFloat float Right int -> B.decimal int-valToBuilder (Object _) = B.fromText "[Object]"+valToBuilder _ (Object _) = B.fromText "[Object]" -valToText :: Value -> Text-valToText (String x) = x-valToText Null = "null"-valToText (Bool True) = "t"-valToText (Bool False) = "f"-valToText (Number x) = +valToText :: Config -> Value -> Text+valToText _ (String x) = x+valToText Config{..} Null = nullValueString+valToText Config{..} (Bool True) = trueString+valToText Config{..} (Bool False) = falseString+valToText _ (Number x) = case floatingOrInteger x of Left float -> T.pack . show $ float Right int -> T.pack . show $ int-valToText (Object _) = "[Object]"+valToText _ (Object _) = "[Object]"
README.md view
@@ -97,9 +97,11 @@ ## Usage ```-jsontsv +jsontsv -Usage: jsontsv FIELDS [-a DELIM] ([-c|--csv] | [-d DELIM]) [-H] [--debug]+Usage: jsontsv FIELDS [-a DELIM] ([-c|--csv] | [-d DELIM]) [-H]+ [-n|--null-string STRING] [-t|--true-string STRING]+ [-f|--false-string STRING] [--debug] Transform JSON objects to TSV. On STDIN provide an input stream of whitespace-separated JSON objects. @@ -110,6 +112,9 @@ -c,--csv Output CSV -d DELIM Output field delimiter. Defaults to tab. -H Include headers+ -n,--null-string STRING String to represent null value. Default: 'null'+ -t,--true-string STRING String to represent boolean true. Default: 't'+ -f,--false-string STRING String to represent boolean false. Default: 'f' --debug Debug keypaths See https://github.com/danchoi/jsontsv for more information.@@ -165,8 +170,8 @@ JSON leaf values are printed as follows: * Strings and numbers are copied to output.-* Boolean values are output as `t` or `f`.-* null is printed as `null`+* Boolean values are output as `t` or `f`. You can changes this with the -t and -f options.+* null is printed as `null`. You can change this with the -n option. * If the leaf value is an array, it is concatenated into a single comma-separated string. This delimiter can be changed with the `-a` option. @@ -194,6 +199,7 @@ * [table](https://github.com/danchoi/table) * [jsonsql](https://github.com/danchoi/jsonsql) * [tsvsql](https://github.com/danchoi/tsvsql)+* [jsonxlsx](https://github.com/danchoi/jsonxlsx) ## Further reading
jsontsv.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/ name: jsontsv-version: 0.1.4.5+version: 0.1.4.6 synopsis: JSON to TSV transformer homepage: https://github.com/danchoi/jsontsv description: Transforms JSON into tab-separated line-oriented output, for easier processing in Unix-style pipelines.