2017-06-13 4 views
2

ich ein Objekt, das ein bisschen so aussieht zu analysieren:FromJSON eine Liste von mehreren Feldern machen

{ 
    "data": 
    [ 
    { 
     "virtio0": "some text", 
     "virtio1": "blah", 
     "ide2": "some other text", 
     "cores": 1, 
     "mem": 512, 
     ... 
    }, 
    { 
     // The same ... 
    } 
    ] 
} 

Jetzt im Grunde analysieren möchte ich diese nummerierten Felder, die in eine [VM], aber mein Problem ist, . Abhängig von der VM-Konfiguration, kann es sein, dass es virtioX-Felder oder ideX-Felder gibt oder nicht. Und ich sehe keine Möglichkeit, im Voraus zu wissen oder die Zahlen zu erraten. Ich dachte, das Beste wäre, einen Disketten-Typ zu definieren, der so etwas wie Virtio | enthalten würde Sata | IDE und so weiter für den Typ und ein Textfeld für den Wert, dann haben jede VM eine [Festplatte] in ihrem Typ. Etwas wie folgt aus:

data DiskType = Virtio | Sata | IDE 
data Disk  = Disk {diskType :: DiskType, diskPath :: Text} 
data VM  = VM {cores :: Int, disks :: [Disk], mem :: Int, ...} 

das wäre toll, aber wie analysieren ich diese Zufallsfelder, die ich direkt in der VM JSON-Objekt in eine Liste haben?

+0

Ich würde nur die Schlüssel als zusätzliche Werte speichern. –

+0

Was meinst du? Ich habe im Grunde keine Ahnung, wie Aeson funktioniert, ich habe kürzlich herausgefunden, wie man v.: "Stuff" benutzt, um andere Namen zu verwenden, vielleicht eine Bedingung, aber das ist es auch schon. Und der Doc ist ein bisschen haarig für mich – Ulrar

+0

Ich bin nicht sehr vertraut mit dem, was Aeson unterstützt. Ein Ansatz besteht darin, "decode" in einen "Object" -Typ zu verwenden. Und analysieren Sie dieses Objekt in Ihre Datentypen. Der Abschnitt "Dekodieren eines gemischten Objekts" könnte nützlich sein, um zu sehen. –

Antwort

5

Während ich mich nicht als Haskell-Experte und noch weniger als Aeson-Experte verstehe, glaube ich, dass ich etwas gefunden habe, das funktioniert. Nimm es für das, was es ist.

der folgende Code all Nutzung dieser Moduldeklaration und diese Importe macht:

{-# LANGUAGE OverloadedStrings #-} 
module Main where 

import Control.Applicative ((<$>), (<|>)) 
import Data.Aeson 
import Data.ByteString.Lazy (ByteString) 
import Data.HashMap.Lazy (HashMap, foldlWithKey') 
import Data.Foldable (toList) 
import Data.Text (Text, stripPrefix, unpack) 
import Text.Read (readMaybe) 

ich leicht die Art Erklärungen geändert:

data DiskType = Virtio | Sata | IDE deriving (Show) 
data Disk = 
    Disk { diskType :: DiskType, diskNumber :: Int, diskPath :: Text } 
    deriving (Show) 
data VM = VM { cores :: Int, disks :: [Disk], mem :: Int } deriving (Show) 

Der bemerkenswerteste Unterschied ist, dass ich diskNumber zu dem addierte Disk Typ, so dass es sowohl die Nummer nach dem Plattentyp als auch den Text, der mit der Platteneigenschaft verknüpft ist, erfassen kann.

Die andere Änderung war, dass ich alle Arten Instanzen von Show gemacht habe. Dies war nur in der Lage zu testen, ob mein Code funktioniert oder nicht.

Zuerst definiert ich eine kleine Hilfsfunktion, die die Nummer nach einer bestimmten Vorwahl finden:

findNumber :: Read a => Text -> Text -> Maybe a 
findNumber prefix candidate = 
    stripPrefix prefix candidate >>= (readMaybe . unpack) 

Beispiele:

*Main Data.Text> findNumber (pack "ide") (pack "ide2") :: Maybe Int 
Just 2 
*Main Data.Text> findNumber (pack "sata") (pack "sata0") :: Maybe Int 
Just 0 
*Main Data.Text> findNumber (pack "foo") (pack "bar") :: Maybe Int 
Nothing 

Dies ermöglichte es mir, eine Funktion zu schreiben, die alle Laufwerke findet in einem Object:

findDisks :: HashMap Text Value -> [Disk] 
findDisks = foldlWithKey' folder [] 
    where 
    findVirtio k s = flip (Disk Virtio) s <$> findNumber "virtio" k 
    findSata k s = flip (Disk Sata) s <$> findNumber "sata" k 
    findIde k s = flip (Disk IDE) s <$> findNumber "ide" k 
    folder acc k (String s) = 
     acC++ toList (findVirtio k s <|> findSata k s <|> findIde k s) 
    folder acc _ _ = acc 

Object ist eine Art Alias ​​fo r HashMap Text Value, also nimmt diese Funktion eine Object als Eingabe und gibt eine Liste der Disk Werte zurück, die es finden konnte.

Dies ist genug, um eine Instanz von FromJSON für VM zu definieren:

instance FromJSON VM where 
    parseJSON = withObject "VM" $ \o -> do 
    let disks = findDisks o 
    cores <- o .: "cores" 
    mem <- o .: "mem" 
    return $ VM cores disks mem 

Um zu testen, ob das funktioniert, ich dieses JSON-String erstellt:

myJson :: ByteString 
myJson = 
    "[\ 
    \{\ 
     \\"virtio0\": \"some text\",\ 
     \\"virtio1\": \"blah\",\ 
     \\"ide2\": \"some other text\",\ 
     \\"cores\": 1,\ 
     \\"mem\": 512\ 
    \}\ 
    \]" 

und verwenden es von main:

main :: IO() 
main = do 
    let vms = decode myJson :: Maybe [VM] 
    print vms 

Wenn exe Cuted, druckt es den decodierten Wert:

Just [VM {cores = 1, disks = [Disk {diskType = IDE, diskNumber = 2, diskPath = "some other text"},Disk {diskType = Virtio, diskNumber = 1, diskPath = "blah"},Disk {diskType = Virtio, diskNumber = 0, diskPath = "some text"}], mem = 512}] 

Beachten Sie, dass der JSON, der hier analysiert wird, einfach ein Array von VM-Objekten ist. Ich habe das äußere Behälterobjekt mit der data Eigenschaft nicht eingeschlossen, aber wenn Sie Hilfe damit benötigen, denke ich, dass das eine unterschiedliche Frage sein sollte :)

+0

Nun, ich habe das gerade getestet und es tut genau das, was ich brauche! Vielen Dank dafür, ich kann nicht sagen, dass ich alles verstehe, also sieht es so aus, als hätte ich ein bisschen zu lesen :) – Ulrar

1

Wenn, wie Sie gesagt, es sind nur 9 virtio und 2 ide, eine einfache und vielleicht nicht so elegent Art und Weise zu tun, um die asum Funktion von Data.Foldable zu verwenden ist (die Wahl aus verschiedenen Parsing-Bibliotheken verallgemeinert)

import Control.Applicative 


instance FromJSON VM where 
    parseJSON = withObject "VM" $ \o -> do 
    cores <- o .: "cores" 
    mem <- o .: "mem" 
    disk <- optional $ asum [ 
     o .: "virtio0", 
     o .: "virtio1", 
     o .: "virtio2", 
    return VM{..} 

Ich habe den Code noch nicht ausprobiert. Weitere Informationen finden Sie unter link für eine umfassende Anleitung zum Haskell-JSON-Parsing mit der Aeson-Bibliothek.

+0

Nun kann ich nicht sagen, wie viele es sein wird, könnte 0 sein, könnte 10 sein, könnte 50. Aber ich kann mir vorstellen, ich könnte es haben Schleife auf 99 Iterationen oder so ähnlich, stelle ich mir vor, dass es ein Maximum geben muss. – Ulrar

Verwandte Themen