2016-04-06 32 views
0

So, ich habe etwas von einem Roadblock mit Parsing der folgenden JSON mit der Haskell Aeson-Bibliothek getroffen.Verschachtelte verschachtelte heterogene JSON-Arrays mit Aeson

So sagen, ich habe folgendes:

"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... 

Und ich würde im Idealfall möchte dies mit Datentypen wie folgt analysieren:

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 

Und so etwas wie die folgenden erhalten:

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

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

Dieses JSON-Beispiel beschreibt Netzwerkpakete und jedes Paket hat einen anderen Namen (z. B. "packetX _Name "), die nicht auf die gleiche Weise geparst werden kann" field1 "oder" field2 ". Es wird jedes Mal anders sein. Die meisten der Aeson-Beispiele da draußen sind ziemlich wenig hilfreich, wenn es um solche Situationen geht. Ich habe eine Funktion in der API-Dokumentation bemerkt withArray genannt, die auf einem String passt, aber ich bin an einem verlieren, was für (Array -> Parser a)

Der Teil I auf bin stecken wirklich zu verwenden, um die heterogene Array Parsen, dass beginnt mit einem String- "Container" und hat dann ein Array mit allen Objekten darin. Bis jetzt habe ich direkt auf die Anordnung von Objekten getippt, aber das Typsystem fing an, ein echtes Labyrinth zu werden und ich fand es wirklich schwierig, sich diesem auf eine Art und Weise zu nähern, die nicht hässlich und hackisch ist. Darüber hinaus liefert Aeson keine sehr hilfreichen Fehlermeldungen.

Irgendwelche Ideen, wie man das angeht?

Antwort

1

In komplizierteren Beispielen wie diese, ist es gut im Auge zu behalten, dass unterhalb der Aeson Value Art sind einfache Datenstrukturen - Vector für Arrays und HashMap für Objekte. Ein bisschen exotischer als die Listen und Karten, die wir gewohnt sind zu behandeln, aber immer noch Datenstrukturen, die Foldable und Traversable Instanzen haben. In diesem Sinne können wir diese Fälle erklären:

{-# 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 

Wir haben die Liste der Pakete an Newtype, weil es bereits eine FromJSON Instanz für FromJSON a => FromJSON [a] und es nicht tut, was wir wollen (es, speziell, ist nur ausgestattet mit homogenen Listen).

Sobald wir das tun, können wir die hashmap innerhalb des Objekts in die Hände bekommen und seine Schlüssel und Werte als Tupel durchlaufen. Mapping über die Tupel, wir produzieren eine [Parser ExpectedPacket], die wir sequence in eine Parser [ExpectedPacket]. Ich benutze hier einfach lens, um das langweilige Zeug zu machen, wie zum Beispiel das Konvertieren zwischen gepackten und entpackten Strings oder das Zerlegen der Hashmaps in Schlüssel-Wert-Tupel. Sie können die Pakete text und unordered-containers verwenden, um die gleichen Ziele zu erreichen, wenn Sie nicht in lens ziehen möchten.

Es scheint am Beispiel arbeiten zu finden:

λ> 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"}]}]) 

Schließlich finde ich oft, dass typeMismatch und eitherDecode Verwendung für das Debuggen von Aeson Fällen enorm hilfreich sein.

+0

Ja, das funktioniert auch für mich. Vielen Dank! Das ist eine der cooleren Anwendungen von Listen-Comprehensions, die ich bisher gesehen habe.Ich habe vorher mit Vectors und HashMaps experimentiert, aber die Typen würden auf Value zurückgeführt, auf eine Art und Weise, die ich schwer zu debuggen fand. – carpemb