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 :)
Ich würde nur die Schlüssel als zusätzliche Werte speichern. –
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
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. –