开发者

My use of Haskell's Text.JSON considered ugly?

What I am trying to do is really simple.

I'd like to convert the following JSON, which I'm getting from an external source:

[{"symbol": "sym1", "description": "desc1"}
 {"symbol": "sym1", "description"开发者_开发知识库: "desc1"}]

into the following types:

data Symbols = Symbols [Symbol]
type Symbol  = (String, String)

I ended up writing the following code using Text.JSON:

instance JSON Symbols where
  readJSON (JSArray arr) = either Error (Ok . Symbols) $ resultToEither (f arr [])
    where
      f ((JSObject obj):vs) acc = either Error (\x -> f vs (x:acc)) $ resultToEither (g (fromJSObject obj) [])
      f [] acc                  = Ok $ reverse acc
      f _ acc                   = Error "Invalid symbol/description list"

      g ((name, JSString val):vs) acc = g vs ((name, fromJSString val):acc)
      g [] acc                        = valg acc
      g _ acc                         = Error "Invalid symbol/description record"

      valg xs = case (sym, desc) of
        (Nothing, _)            -> Error "Record is missing symbol"
        (_, Nothing)            -> Error "Record is missing description"
        (Just sym', Just desc') -> Ok (sym', desc')
        where
          sym = lookup "symbol" xs
          desc = lookup "description" xs

  showJSON (Symbols syms) = JSArray $ map f syms
    where
      f (sym, desc) = JSObject $ toJSObject [("symbol", JSString $ toJSString sym),
                                             ("description", JSString $ toJSString desc)]

This has got to the the most inelegant Haskell I've ever written. readJSON just doesn't look right. Sure, showJSON is substantially shorter, but what is up with this JSString $ toJSString and JSObject $ toJSObject stuff I am forced to put in here? And resultToEither?

Am I using Text.JSON wrong? Is there a better way?


Okay this is more like it. I've gotten readJSON down to the following thanks to the clarifications and ideas from Roman and Grazer. At every point it will detect an incorrectly formatted JSON and output an error instead of throwing an exception.

instance JSON Symbols where
  readJSON o = fmap Symbols (readJSON o >>= mapM f)
    where
      f (JSObject o) = (,) <$> valFromObj "symbol" o <*> valFromObj "description" o
      f _            = Error "Unable to read object"


Could you please change the title to something more precise? From "Haskell's Text.JSON considered ugly …" to something like "My code using Text.JSON considered ugly..."

Half of your code consists of explicit recursion -- why do you need it? From a quick look something like mapM should suffice.

Update: sample code

instance JSON Symbols where
  readJSON (JSArray arr) = fmap Symbols (f arr)
  f = mapM (\(JSObject obj) -> g . fromJSObject $ obj)
  g = valg . map (\(name, JSString val) -> (name, fromJSString val))

  valg xs = case (sym, desc) of
    (Nothing, _)            -> Error "Record is missing symbol"
    (_, Nothing)            -> Error "Record is missing description"
    (Just sym', Just desc') -> Ok (sym', desc')
    where 
      sym = lookup "symbol" xs
      desc = lookup "description" xs


Rearranging a little from Roman's nice solution. I think this may be a little more readable.

instance JSON Symbols where
  readJSON o = fmap Symbols (readJSON o >>= mapM f)
    where
      f (JSObject o) = let l = fromJSObject o
                       in do s <- jslookup "symbol" l
                             d <- jslookup "description" l
                             return (s,d)
      f _ = Error "Expected an Object"
      jslookup k l = maybe (Error $ "missing key : "++k) readJSON (lookup k l)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜