2011-07-26 22 views
13

Estoy tratando de analizar JSON de la siguiente forma usando Aesoncómo analizar JSON anidada con Aeson

{"field":{"name":"..."}} 

or 

{"tag":{"name":"..."}} 

or 

{"line":{"number":"..."}} 

para construir el siguiente tipo de datos

data Rule = Line Integer 
      | Field L.ByteString 
      | Tag L.ByteString 

Por desgracia, se enfrentan a dos problemas que me no he encontrado soluciones, a saber:

  1. ¿Cómo analizo JSON? En cuanto a la implementación de (.:), utiliza la búsqueda para extraer el valor de una clave específica. Tengo dudas de hacer algo como esto ya que parece depender demasiado de los detalles de cómo Aeson implementa las cosas. ¿Me equivoco al pensar que esto es un problema?

  2. ¿Cómo uso el constructor de datos correcto en función de qué clave está presente en el JSON? Todos mis esfuerzos con < |> no me han llevado a ninguna parte.

Publicaba el código que he escrito hasta ahora, pero ni siquiera llegué al punto en el que tengo algo que valga la pena publicar.

Respuesta

9

¿Qué tal lo siguiente?

{-# LANGUAGE OverloadedStrings #-} 

import Control.Applicative 
import   Data.Aeson 
import   Data.Aeson.Types 
import qualified Data.ByteString  as B 
import qualified Data.ByteString.Lazy as L 
import qualified Data.Map    as M 

data Rule = Line Integer 
      | Field L.ByteString 
      | Tag L.ByteString 
      deriving Show 

instance FromJSON Rule where 
    parseJSON j = do 
    o <- parseJSON j -- takes care of JSON type check 
    case M.toList (o :: Object) of 
     [("field", Object o')] -> Field <$> o' .: "name" 
     [("tag", Object o')] -> Tag <$> o' .: "name" 
     [("line", Object o')] -> Line <$> o' .: "number" 
     _      -> fail "Rule: unexpected format" 
+0

¡Muchas gracias, esto es exactamente lo que estaba buscando! Hice un pequeño cambio en las dos primeras líneas para hacer que 'parseJSON (Object o) = case M.toList o of', así como agregar un' parseJSON _ = mzero' por separado. –

+0

@luke_randall, por cierto, utilicé 'o <- parseJSON j' en lugar de' mzero' a propósito, ya que 'mzero' no proporciona ninguna información útil sobre lo que salió mal más allá de un simple' "mzero" ', mientras que 'parseJSON' le da mensajes de error como' "cuando espera que un texto de mapa sea una matriz encontrada en su lugar" ' – hvr

+0

Muy bien, eso tiene sentido. Gracias por explicar tu razonamiento. Creo que lo revertiré. –

6

Para este problema creé una función auxiliar que mira hacia arriba una clave:

lookupE :: Value -> Text -> Either String Value 
lookupE (Object obj) key = case H.lookup key obj of 
     Nothing -> Left $ "key " ++ show key ++ " not present" 
     Just v -> Right v 
loopkupE _ _    = Left $ "not an object" 

y usarlo dos funciones que anidan en objetos:

(.:*) :: (FromJSON a) => Value -> [Text] -> Parser a 
(.:*) value = parseJSON <=< foldM ((either fail return .) . lookupE) value 

(.:?*) :: (FromJSON a) => Value -> [Text] -> Parser (Maybe a) 
(.:?*) value = either (\_ -> return Nothing) (liftM Just . parseJSON) 
       . foldM lookupE value 
-- Or more simply using Control.Alternative.optional 
-- (.:?*) value keys = optional $ value .:* keys 

Sólo lookupE depende de la representación interna por lo que es fácil modificarla, si eso cambia. Entonces {"tag":{"name":"..."}} se analiza como v .:* ["tag", "name"]. Tenga en cuenta que también funciona para listas vacías: v .:* [] es equivalente a parseJSON v.

Cuestiones relacionadas