2010-09-10 22 views
5

Lo que estoy tratando de hacer es realmente simple.¿Mi uso de Haskell's Text.JSON se considera feo?

me gustaría convertir el siguiente JSON, que estoy recibiendo de una fuente externa:

[{"symbol": "sym1", "description": "desc1"} 
{"symbol": "sym1", "description": "desc1"}] 

en los siguientes tipos:

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

terminé escribiendo el siguiente código usando 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)] 

Esto tiene para el Haskell más poco elegante que he escrito. readJSON simplemente no se ve bien. Claro, showJSON es sustancialmente más corto, pero ¿qué pasa con este JSString $ toJSString y JSObject $ toJSObject cosas que estoy obligado a poner aquí? ¿Y resultToEither?

¿Estoy usando Text.JSON incorrecto? ¿Hay una mejor manera?


Bueno, esto es más como él. Obtuve readJSON de la siguiente manera gracias a las aclaraciones e ideas de Roman y Grazer. En cada punto detectará un JSON formateado incorrectamente y generará un error en lugar de lanzar una excepción.

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" 

Respuesta

6

¿Podría cambiar el título por algo más preciso? Desde "Haskell's Text.JSON considerado feo ..." a algo así como "Mi código usando Text.JSON considerado feo ..."

La mitad de tu código consiste en recursión explícita, ¿por qué la necesitas? Desde un vistazo rápido algo como mapM debería ser suficiente.

Actualización: código de ejemplo

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 
+0

Puedo cambiar el título, pero no creo que cambie la pregunta. ¿Cómo usaría mapM aquí donde no hay mónadas involucradas para simplificar? – qrest

+0

'Result' * es * una mónada (http://hackage.haskell.org/packages/archive/json/0.4.4/doc/html/Text-JSON.html#t:Result). Ahora actualizaré mi respuesta con un código de muestra. Tenga en cuenta que descarté algunos mensajes de error; puede restaurarlos si lo desea. –

+0

Para mejorar el manejo de errores (el código que mostré puede arrojar excepciones) escriba versiones seguras de funciones de coincidencia de patrones (para JSObject y JSString) que devolverá Error en la falla de coincidencia de patrones y los usará monádicamente. –

2

Reorganización de un poco de buena solución de Roman. Creo que esto puede ser un poco más legible.

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

Lo que hay en esta solución es parte de lo que estaba tratando de hacer al preguntar sobre toJSObject, toJSString, rarezas. jslookup, por ejemplo, ilustra que readJSON puede extraer efectivamente una cadena de un JSValue sin tener que pasar por todo eso. – qrest

+0

El 'fromJSObject' probablemente ni siquiera sería necesario si hubiera una instancia JSON almacenada en JSObject. En realidad, si el paquete json está compilado con MAP_AS_DICT, entonces debería ser tan simple como: readJSON :: Result [Map String String]. Luego, algunos simples ajustes para convertir el mapa en la estructura que desea –

Cuestiones relacionadas