Json解析器,错误地将字符串解析为数字

时间:2013-07-24 06:22:49

标签: json haskell parsec

我对Haskell和函数式编程一般都很陌生,所以我正在用Parsec编写一个小程序来解析JSON,并将其作为学习基本概念的一种手段。这就是我到目前为止所做的:

import Text.Parsec
import Text.Parsec.String

data JValue = JString String
            | JNumber Double
            | JBool Bool
            | JNull
            | JObject [(String, JValue)]
            | JArray [JValue]
              deriving (Eq, Ord, Show)

parseJString, parseJNumber, parseJBool, parseJNull :: Parser JValue
parseJString = do
    str <- between (char '"') (char '"') (many (noneOf "\""))
    return . JString $ str

parseJNumber = do
    num <- many digit
    return . JNumber . read $ num

parseJBool = do
    val <- string "true" <|> string "false"
    case val of
        "true"  -> return (JBool True)
        "false" -> return (JBool False)

parseJNull = string "null" >> return JNull

parseJValue :: Parser JValue
parseJValue =   parseJString 
            <|> parseJNumber 
            <|> parseJBool 
            <|> parseJNull

现在,我假设这些数字是整数。单独地,parseJStringparseJNumberparseJBoolparseJNull在ghci中按预期工作。此外,parseJValue正确解析字符串和数字。

ghci> parse parseJString "test" "\"test input\""
Right (JString "test input")
ghci> parse parseJNumber "test" "345"
Right (JNumber 345.0)
ghci> parse parseJBool "test" "true"
Right (JBool True)
ghci> parse parseJNull "test" "null"
Right JNull
ghci> parse parseJValue "test" "\"jvalue test\""
Right (JString "jvalue test")
ghci> parse parseJValue "test" "789"
Right (JNumber 789.0)
但是,当我尝试解析parseJValuetruefalse时,

null失败了,我收到了一个有趣的错误。

ghci> parse parseJValue "test" "true"
Right (JNumber *** Exception: Prelude.read: no parse

我得到了一个成功的解析,但是解析返回JNumber,后面跟一个错误说明Prelude.read失败了。我觉得我在构建解析器时缺少一些核心概念,但我无法看到我出错的地方。另外,我是否在使用我的代码时犯了任何初学者的错误,即是否会将这些错误视为“坏”的haskell?

2 个答案:

答案 0 :(得分:2)

问题是在many中使用parseJNumber。当没有使用以下字符串的字符时,它也是一个有效的解析(“许多p将解析器应用于零次或多次。[...]”)。您需要的是many1

parseJNumber = do
  num <- many1 (oneOf "0123456789")
  return $ JNumber (read num :: Double)

编辑:

不知何故,我认为你(.)($)的组合看起来很奇怪。当我懒得写括号时,我可以使用(。)来摆脱函数参数(比如使用(>>=))和($)。在函数parseJString中,您不需要(.)才能获得正确的绑定优先级。 (我在上面的代码中进行了相同的转换。)

parseJString = do
  str <- between (char '"') (char '"') (many (noneOf "\""))
  return $ JString str

此外,您可以通过重构parseJBool来消除代码重复:

parseJBool = do
  val <- string "true" <|> string "false"
  return (case val of
    "true"  -> JBool True
    "false" -> JBool False)

我甚至会将case-construct重写为(total)local函数:

parseJBool = (string "true" <|> string "false") >>= return . toJBool
 where
  -- there are only two possible strings to pattern match
  toJBool "true" = JBool True
  toJBool _      = JBool False

最后但并非最不重要的是,您可以轻松地将其他功能转换为使用(>>=)而非do-blocks。

-- additionally, you do not need an extra type signature for `read`
-- the constructor `JNumber` already infers the correct type
parseJNumber =
  many1 (oneOf "0123456789") >>= return . JNumber . read

parseJString =
  between (char '"') (char '"') (many (noneOf "\"")) >>= return . JString

答案 1 :(得分:1)

您应该尝试使用many1 digit而不是many digitmany成功出现参数零。

比较

ghci> parse (many digit) "test" "true"
Right ""
ghci> parse (many1 digit) "test" "true"
unexpected "t"
expecting digit

因此,在您的情况下,parseJNumber中的parseJValue将成功并返回一个空字符串,然后传递给read。但是read "" :: Double失败了。