2016-04-06 1 views
0

J'ai donc rencontré un problème de roadblock avec l'analyse du fichier JSON suivant avec la bibliothèque Haskell Aeson.Analyse de tableaux JSON hétérogènes imbriqués avec Aeson

dire Je donne les résultats suivants:

"packetX_Name": [ 
    "container", 
    [ 
    { 
     "field1": "value1", 
     "field2": "value2" 
    }, 
    { 
     "field1": "value3", 
     "field2": "value4" 
    }, 
    { 
     "field1": "value5", 
     "field2": "value6" 
    } 
    ] 
], 
"packetY_Name": [ 
    "container", 
    [ 
    { 
     "field1": "value7", 
     "field2": "value8" 
    }, 
    { 
     "field1": "value9", 
     "field2": "value10" 
    } 
    ] 
], 
etc... 

Et je voudrais idéalement analyser cela en utilisant des types de données comme ceci:

data ExtractedPacket = ExtractedPacket 
    { packetName :: String 
    , packetFields :: [ExtractedPacketField] 
    } deriving (Show,Eq) 

instance FromJSON ExtractedPacket where 
    parseJSON = blah 

data ExtractedPacketField = ExtractedPacketField 
    { field1 :: String 
    , field2 :: String 
    } deriving (Show,Eq) 

instance FromJSON ExtractedPacketField where 
    parseJSON = blah 

Et obtenir quelque chose comme ce qui suit:

ExtractedPacket 
    "packetX_Name" 
    [ ExtractedPacketField "value1" "value2" 
    , ExtractedPacketField "value3" "value4" 
    , ExtractedPacketField "value5" "value6" 
    ] 

ExtractedPacket 
"packetY_Name" 
    [ ExtractedPacketField "value7" "value8" 
    , ExtractedPacketField "value10" "value10" 
    ] 

Cet exemple JSON décrit des paquets réseau et chaque paquet porte un nom différent (par exemple "packetX" _Name ") qui ne peut pas être analysé de la même manière que" field1 "ou" field2 ". Ce sera différent à chaque fois. La plupart des exemples d'Aeson sont très peu utiles dans des situations comme celle-ci. Je l'ai remarqué une fonction dans la documentation de l'API appelée withArray qui correspond à un String, mais je suis à perdre à ce qu'il faut utiliser pour (Array -> Parser a)

La partie que je suis vraiment coincé sur le tableau est l'analyse hétérogène commence par un "conteneur" String, puis a un tableau avec tous les objets qui s'y trouvent. Jusqu'à présent, j'ai indexé directement sur le tableau des objets, mais le système de types a commencé à devenir un vrai labyrinthe et j'ai trouvé très difficile d'aborder ce problème d'une manière qui n'est pas laide et hachée. En plus de cela, Aeson ne produit pas de messages d'erreur très utiles.

Des idées sur la façon d'aborder cela?

Répondre

1

Dans les exemples plus complexes comme ceux-ci, il est bon de garder à l'esprit que, sous le type Aeson Value sont des structures de données simples - Vector pour les tableaux et HashMap pour les objets. Un peu plus exotique que les listes et les cartes auxquelles nous sommes habitués, mais les structures de données qui ont Foldable et Traversable instances. Dans cet esprit, nous pouvons déclarer ces cas:

{-# LANGUAGE OverloadedStrings #-} 

import qualified Control.Lens as Lens 
import qualified Data.Foldable as Foldable 
import qualified Data.Text.Strict.Lens as Lens 
import   Data.Aeson 
import   Data.Aeson.Types 

newtype ExtractedPackets = 
    ExtractedPackets [ExtractedPacket] deriving (Show) 

instance FromJSON ExtractedPackets where 
    parseJSON (Object o) = do 
    let subparsers = 
      [ ExtractedPacket (Lens.view Lens.unpacked key) <$> parseJSON packets 
      | (key, Array values) <- Lens.itoList o 
      , [email protected](Array _) <- Foldable.toList values] 
    packets <- sequence subparsers 
    return (ExtractedPackets packets) 
    parseJSON invalid = 
    typeMismatch "ExtractedPackets" invalid 

instance FromJSON ExtractedPacketField where 
    parseJSON (Object o) = 
    ExtractedPacketField <$> o .: "field1" <*> o .: "field2" 
    parseJSON invalid = 
    typeMismatch "ExtractedPacketField" invalid 

Nous devons newtype la liste des paquets car il y a déjà une instance FromJSON pour FromJSON a => FromJSON [a] et il ne fait pas ce que nous voulons (ce, en particulier, est seulement équipé pour traiter des listes homogènes). Une fois que nous faisons cela, nous pouvons mettre la main sur le hashmap à l'intérieur de l'objet et parcourir ses clés et valeurs sous forme de tuples. Mapping sur les tuples, nous produisons un [Parser ExpectedPacket], que nous pouvons sequence dans un Parser [ExpectedPacket]. J'utilise généreusement lens ici pour faire les trucs ennuyeux, comme la conversion entre les chaînes emballées et non emballées ou la décomposition de la hashmap en tuples clé-et-valeur. Vous pouvez utiliser les packages text et unordered-containers pour atteindre les mêmes objectifs si vous ne souhaitez pas insérer lens.

Il semble fonctionner sur l'exemple fourni:

λ> eitherDecode bytes :: Either String ExtractedPackets 
Right (ExtractedPackets [ExtractedPacket {packetName = "packetX_Name", 
packetFields = [ExtractedPacketField {field1 = "value1", field2 = 
"value2"},ExtractedPacketField {field1 = "value3", field2 = 
"value4"},ExtractedPacketField {field1 = "value5", field2 = 
"value6"}]},ExtractedPacket {packetName = "packetY_Name", packetFields 
= [ExtractedPacketField {field1 = "value7", field2 = 
"value8"},ExtractedPacketField {field1 = "value9", field2 = 
"value10"}]}]) 

Enfin, je trouve souvent que l'utilisation typeMismatch et eitherDecode être extrêmement utile pour le débogage des instances AESON.

+0

Oui, cela fonctionne pour moi aussi. Je vous remercie! C'est l'une des utilisations les plus cool de la compréhension des listes que j'ai vu jusqu'ici.Je travaillais avec Vectors et HashMaps auparavant, mais les types seraient déduits de Value d'une manière que je trouvais difficile à déboguer. – carpemb