{-# LANGUAGE Haskell2010 #-} {-# OPTIONS -Wall -O2 -fno-warn-unused-do-bind -fno-warn-missing-signatures -fno-warn-name-shadowing -fno-warn-orphans #-} {- | Module : DeadSimpleJSON.hs Copyright : (c) Julian Fleischer License : MIT Maintainer : Julian Fleischer Stability : experimental Portability : portable A simple approach for parsing JSON. To read JSON data use 'read'. To print JSON data use 'show': > let jsonData = read "[1,2,4,8,16]" :: JSON > putStrLn $ show jsonData You can query json data using '?'. Querying implies conversion, therefor you may need to specify the result type: > let jsonData = read "{\"seven\": 7, \"nine\": [1,2,4,8,16]}" > print $ (jsonData ? "nine[3]" :: Int) For tighter control use 'parse'. A more convenient way for creating JSON objects in source code or querying JSON data, is using Template Haskell. See @Text.SimpleJSON.TH@. The recommended way for importing this module is importing it qualified, like so: > import qualified Text.SimpleJSON as JSON > import Text.SimpleJSON (JSON) -} module Text.DeadSimpleJSON ( -- * Parsing strings parse, parse', parseM, -- * Basic data types Value (..), JSON, -- ** Querying json data (?), top, -- ** Conversion to and from Convert (..) ) where import Prelude hiding (True, False) import qualified Prelude import Data.Char (isControl) import qualified Data.Map as M import qualified Data.Vector as V import Text.Parsec hiding (parse) import Control.Applicative ((*>), (<*)) import Data.Functor.Identity import Numeric (showHex, readHex) import Text.DeadSimpleJSON.Convert import Text.DeadSimpleJSON.Query import Text.DeadSimpleJSON.Types instance Show JSON where show (JSON jsonObject) = write jsonObject instance Read JSON where readsPrec _ = either (const []) (:[]) . runIdentity . runParserT json () "-" parse :: String -> Either ParseError JSON -- ^ Parse a String for JSON data or return a ParseError. parse str = runIdentity $ runParserT json' () "-" str parse' :: String -> Maybe Value -- ^ Parses a top-level JSON object, returning Just a Value or Nothing. parse' = parseM parseM :: Monad m => String -> m Value -- ^ Purely Monadic version of 'parse''. parseM = either (fail . show) (return . top) . parse top :: JSON -> Value -- ^ Unwraps a top-level JSON object to a Value. top (JSON v) = v write x = case x of (Number n e) -> writeNumber n e (String s) -> '"' : writeString s (Object m) -> writeObject m (Array v) -> writeArray v True -> "true" False -> "false" Null -> "null" writeNumber n 0 = show n writeNumber n e = show n ++ "e" ++ show e writeString (x:xs) | isControl x = "\\u" ++ showHex (fromEnum x) (writeString xs) | x == '\x2028' = "\\u2028" | x == '\x2029' = "\\u2029" | x == '\\' = "\\\\" ++ writeString xs | x == '\"' = "\\\"" ++ writeString xs | otherwise = x : writeString xs writeString [] = "\"" writeArray arr | V.null arr = "[]" | otherwise = '[' : (tail $ concat $ V.foldr (\v l -> "," : write v : l) ["]"] arr) -- note that using V.foldr' instead of V.foldr makes the SpecConstr optimizer go nuts (-fspec-constr) writeObject obj | M.null obj = "{}" | otherwise = '{' : (tail $ concat $ M.foldrWithKey' (\k v l -> ",\"" : writeString k : ":" : write v : l) ["}"] obj) json' :: Monad m => ParsecT String () m JSON json :: Monad m => ParsecT String () m (JSON, String) value, jsonString, number, negativeNumber, nonNegativeNumber, object, array :: Monad m => ParsecT String () m Value stringChar, escapedChar :: Monad m => ParsecT String () m Char keyValue :: Monad m => ParsecT String () m (String, Value) mkNumber :: Monad m => String -> String -> String -> ParsecT String u m Value json = do val <- spaces *> (object <|> array) rest <- many anyToken return (JSON val, rest) json' = do val <- spaces >> (object <|> array) <* spaces (char '\EOT' >> return ()) <|> eof return $ JSON val value = jsonString <|> number <|> object <|> array <|> (string "true" >> return True) <|> (string "false" >> return False) <|> (string "null" >> return Null) jsonString = do char '"' str <- many (stringChar <|> escapedChar) char '"' return $ String str stringChar = satisfy (\x -> not (isControl x || (elem x "\\\""))) escapedChar = do char '\\' oneOf "\"\\/" <|> (oneOf "bfnrt" >>= special) <|> (char 'u' >> count 4 hexDigit >>= convert) "escape sequence: one of b, f, n, r, t, or uXXXX" where special chr = return $ case chr of 'b' -> '\b' 'f' -> '\f' 'n' -> '\n' 'r' -> '\r' 't' -> '\t' _ -> undefined convert str = do let [(hex, _)] = readHex str return $ toEnum hex number = negativeNumber <|> nonNegativeNumber negativeNumber = do char '-' (Number nom denom) <- nonNegativeNumber return $ Number (negate nom) denom nonNegativeNumber = do num <- digits <|> string "0" mantisse <- option "" (char '.' *> many1 digit) exp <- option "0" exponent mkNumber num mantisse exp where digits = do first <- oneOf ['1'..'9'] rest <- many digit return $ first : rest exponent = do oneOf "eE" sign <- option '+' $ oneOf "+-" exp <- many1 digit return $ sign : exp mkNumber str1 str2 ('+':str3) = mkNumber str1 str2 str3 mkNumber str1 str2 str3 = do let str2' = reverse $ dropWhile (== '0') $ reverse str2 [(nom, _)] = reads (str1 ++ str2') [(exp, _)] = reads (str3) exp' = negate (fromIntegral (length str2') + negate exp) num n d | n == 0 = Number 0 0 | n `rem` 10 == 0 = num (n `quot` 10) (d+1) | otherwise = Number n d return $ num nom exp' object = do pairs <- between (char '{') (char '}') $ do spaces sepBy keyValue (char ',' >> spaces) return $ Object $ M.fromList pairs keyValue = do (String key) <- jsonString <* char ':' val <- between spaces spaces value return (key, val) array = do values <- between (char '[') (char ']') $ do spaces sepBy (value <* spaces) (char ',' >> spaces) return $ Array $ V.fromList values