2014-07-14 16 views
9

Ho un documento JSON che assomiglia:il parsing di un array con Haskell Aeson

{ "series": [[1,2], [2,3], [3,4]] } 

mi piacerebbe analizzare questo in una serie di tipi di dati:

data Series = Series [DataPoint] 
data DataPoint = DataPoint Int Int -- x and y 

sto avendo molti problemi nel tentativo di scrivere l'istanza FromJSON per DataPoint.

instance FromJSON DataPoint where 
    parseJSON (Array a) = ??? 

Ho provato con obiettivo di distruggere il record DataPoint, ma non compila:

case a ^.. values . _Integer of -} 
    [x,y] -> DataPoint <$> x <*> y 
    _  -> mzero 

che non riesce con questo errore (le prime due righe ottengo addirittura assente l'inganno della lente , solo cercando di creare un DataPoint <$> 1 <*> 2):

Couldn't match type ‘aeson-0.7.0.6:Data.Aeson.Types.Internal.Parser 
         Integer’ 
       with ‘Integer’ 
Expected type: (aeson-0.7.0.6:Data.Aeson.Types.Internal.Parser 
        Integer 
       -> Const 
        (Data.Monoid.Endo 
         [aeson-0.7.0.6:Data.Aeson.Types.Internal.Parse 
        (aeson-0.7.0.6:Data.Aeson.Types.Internal.Parser I 
       -> Value 
       -> Const 
        (Data.Monoid.Endo 
         [aeson-0.7.0.6:Data.Aeson.Types.Internal.Parser 
        Value 
    Actual type: (Integer 
       -> Const 
        (Data.Monoid.Endo 
         [aeson-0.7.0.6:Data.Aeson.Types.Internal.Parse 
        Integer) 
       -> Value 
       -> Const 
        (Data.Monoid.Endo 
         [aeson-0.7.0.6:Data.Aeson.Types.Internal.Parser 
        Value 
In the second argument of ‘(.)’, namely ‘_Integer’ 
In the second argument of ‘(^..)’, namely ‘values . _Integer’ 

c'è un modo migliore per fare questo?

Qualcuno ha un esempio di analisi di matrici di valori in una struttura più dettagliata?

+0

[Ecco un esempio] (https://gist.github.com/bheklilr/98ac8f8e663cf02fcaa6) ho scritto qualche tempo fa per qualcun altro, potrebbe dare un buon inizio. – bheklilr

+0

Grazie a bheklilr, ma il problema che sto incontrando non è l'analisi di Aeson (l'analisi degli oggetti è abbastanza semplice), ma focalizzata sulla destrutturazione di un array in un tipo di dati più semantico. L'array ha '[X, Y]', dove sono due significati semantici diversi, che sono indicati solo dall'indice. Voglio analizzarlo in un vero tipo di dati 'DataPoint Int Int 'che posso affinare i tipi e i nomi in basso per essere esattamente ciò che dovrebbe significare. – cschneid

risposta

14

Aeson ha istanza per la lista, quindi penso che non sia necessario trattare con i vettori.

{-# LANGUAGE LambdaCase #-} 
import Data.Aeson 

data Series = Series [DataPoint] 
data DataPoint = DataPoint Int Int 

instance FromJSON DataPoint where 
    parseJSON jsn = do 
    [x,y] <- parseJSON jsn 
    return $ DataPoint x y 

instance FromJSON Series where 
    parseJSON = \case 
    Object o -> (o .: "series") >>= fmap Series . parseJSON 
    x -> fail $ "unexpected json: " ++ show x 
+0

Devo ammettere che questa risposta mi piace di più, non ho nemmeno pensato che gli elenchi possano già avere un'istanza. – bheklilr

+0

Sono stato anche sorpreso di vederlo. –

4

Il trucco qui è ottenere l'istanza per FromJSON DataPoint corretta, che richiede un po 'di corrispondenza, ma non è male. Sono venuto con

instance FromJSON DataPoint where 
    parseJSON (Array v) 
     | V.length v == 2 = do 
      x <- parseJSON $ v V.! 0 
      y <- parseJSON $ v V.! 1 
      return $ DataPoint x y 
     | otherwise = mzero 
    parseJSON _ = mzero 

Quale sarà in grado di analizzare in modo pulito, se non è in grado di tirare due Int s per x e y. Allora non resta che definire l'istanza per Series:

instance FromJSON Series where 
    parseJSON (Object o) = do 
     pts <- o .: "series" 
     ptsList <- mapM parseJSON $ V.toList pts 
     return $ Series ptsList 
    parseJSON _ = mzero 

Il che, ancora una volta, sarà in modo pulito fallire se i dati non è valido ovunque. Per verificare:

> decode "{\"series\": [[1, 2], [3, 4]]}" :: Maybe Series 
Just (Series [DataPoint 1 2, DataPoint 3 4]) 
> decode "{\"series\": [[1, 2], [3, {}]]}" :: Maybe Series 
Nothing 

Quindi sembra che funzioni.


EDIT: Come @maxtaldykin ha sottolineato, si può solo trarre vantaggio dell'istanza FromJSON a => FromJSON [a] con

instance FromJSON DataPoint where 
    parseJSON obj = do 
     [x, y] <- parseJSON obj 
     return $ DataPoint x y 

instance FromJSON Series where 
    parseJSON (Object o) = do 
     pts <- o .: "series" 
     fmap Series $ parseJSON pts 
    parseJSON _ = mzero 

Che viene notevolmente semplificata dalla mia risposta originale. Complimenti a Max.

+0

Grazie per la rapida risposta precedente.Mi ha superato il mio ostacolo e ho finito per usare una combinazione sia della tua risposta che di @max taldykin. – cschneid