-- Copyright 2014 Alvaro J. Genial (http://alva.ro) -- see LICENSE.md for more. -- | A Minimal JSON Parser & Printer module Text.JSON.Yocto (decode, encode, Value (..)) where import Control.Applicative hiding ((<|>), many) import Data.Char (chr, isControl, ord) import Data.List (find, intercalate) import Data.Map (fromList, Map, toList) import Data.Maybe (fromJust) import Data.Ratio ((%), denominator, numerator) import Prelude hiding (exp, exponent, null) import Numeric (fromRat, readDec, readHex, showHex) import Text.Parsec -- | Represents arbitrary JSON data. data Value = Null | Boolean Bool | Number Rational | String String | Array [Value] | Object (Map String Value) deriving (Eq, Ord, Read, Show) -- | Encodes a 'Value' to a 'String'. encode :: Value -> String encode Null = "null" encode (Boolean b) = if b then "true" else "false" encode (Number n) = if rem == 0 then show i else show $ fromRat n where (i, rem) = numerator n `divMod` denominator n encode (String s) = "\"" ++ concatMap escape s ++ "\"" encode (Array a) = "[" ++ intercalate "," (encode <$> a) ++ "]" encode (Object o) = "{" ++ intercalate "," (f <$> toList o) ++ "}" where f (n, v) = encode (String n) ++ ":" ++ encode v escape c = maybe control (\e -> '\\' : [e]) (c `lookup` escapes) where control = if isControl c then (escape' . showHex . ord) c else [c] escape' hex = "\\u" ++ replicate (4 - length s) '0' ++ s where s = hex "" escapes = [('\b', 'b'), ('\f', 'f'), ('\n', 'n'), ('\r', 'r'), ('\t', 't'), ('\\', '\\'), ('"', '"')] -- | Decodes a 'Value' from a 'String'. decode :: String -> Value decode = attempt . parse input "JSON" where attempt (Right (success, "")) = success attempt (Right (_, trail)) = error $ "trailing " ++ show trail attempt (Left failure) = error $ "invalid " ++ show failure input = value & getInput where value = lexical $ null <|> boolean <|> number <|> string' <|> array <|> object null = Null <$ string "null" boolean = Boolean <$> (True <$ string "true" <|> False <$ string "false") number = Number <$> rational <$> (integer & fraction & exponent) string' = String <$> between (char '"') (char '"') (many character) array = Array <$> between (char '[') (char ']') (listOf value) object = Object <$> between (char '{') (char '}') (fromList <$> listOf pair) pair = lexical name & (lexical (char ':') >> value) where name = (\(String s) -> s) <$> string' character = escaped <|> satisfy (not . \c -> isControl c || elem c "\"\\") where escaped = char '\\' >> (unescape <$> oneOf "\"\\/bfnrt" <|> unicode) unicode = char 'u' >> (hexadecimal <$> count 4 hexDigit) unescape c = fst . fromJust $ find ((== c) . snd) escapes integer = option '+' (char '-') & (0 <$ char '0' <|> natural) fraction = option 0 (char '.' >> fractional <$> many1 digit) exponent = option 0 (oneOf "eE" >> natural `maybeSignedWith` (plus <|> minus)) where number `maybeSignedWith` sign = ($ 0) <$> option (+) sign <*> number (plus, minus) = ((+) <$ char '+', (-) <$ char '-') a & b = (,) <$> a <*> b listOf = (`sepBy` char ',') lexical = between ws ws where ws = many (oneOf " \t\r\n") natural = decimal <$> many1 digit decimal = fst . head . readDec hexadecimal = chr . fst . head . readHex fractional digits = decimal digits % (10 ^ length digits) rational ((('+', int), frac), exp) = (fromInteger int + frac) * 10 ^^ exp rational ((('-', int), frac), exp) = -(fromInteger int + frac) * 10 ^^ exp