weiß ich nicht, eine gute Strategie zu bekommen, wo Sie seit der ParseJSON Monade nicht ein Transformator sein wollen oder auf Basis von IO. Was Sie leichter tun können, ist, in einen Typ zu decodieren und dann in den zweiten zu übersetzen, wie in einer früheren Frage 'Give a default value for fields not available in json using aeson' getan.
Da große Strukturen mühsam reproduziert werden können, können Sie die Struktur parametrisieren und mit IO Int
oder Int
instanziieren. Zum Beispiel, sagen wir, Sie Feld a
vom Draht aber b
wie zufällig aus dem IO Monade wollte:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
import Data.Aeson
import System.Random
import Data.ByteString.Lazy (ByteString)
data Example' a =
Example { a :: Int
, b :: a
} deriving (Show,Functor,Foldable,Traversable)
type Partial = Example' (IO Int)
type Example = Example' Int
instance FromJSON Partial where
parseJSON (Object o) =
Example <$> o .: "a"
<*> pure (randomRIO (1,10))
loadExample :: Partial -> IO Example
loadExample = mapM id
parseExample :: ByteString -> IO (Maybe Example)
parseExample = maybe (pure Nothing) (fmap Just . loadExample) . decode
Beachten Sie, wie loadExample
nutzt unsere traverse
Instanz die IO-Aktionen innerhalb der Struktur auszuführen. Hier ist ein Beispiel für die Verwendung:
Main> parseExample "{ \"a\" : 1111 }"
Just (Example {a = 1111, b = 5})
Erweiterte
Wenn Sie mehr als eine Art von Feld hatte, für die Sie ein IO-Aktion wollte man konnte entweder
einen Datentyp Stellen für alle. Anstatt b
vom Typ IO Int
können Sie es IO MyComplexRecord
machen. Dies ist die einfache Lösung.
Die komplexere und unterhaltsamere Lösung besteht darin, einen höheren Typparameter zu verwenden.
Für Option 2, betrachten:
data Example' f = Example { a :: Int
, b :: f Int
, c :: f String }
Sie dann Proxy
und Control.Monad.Identity
anstelle von Werten wie IO Int
und Int
bisher verwendeten nutzen könnten.Sie müssen Ihr eigenes Traversal schreiben, da Sie Traverse
für diese Klasse nicht ableiten können (was uns das mapM
liefert, das oben verwendet wird). Wir könnten eine Traversalklasse mit Art (* -> *) -> *
mit ein paar Erweiterungen (RankNTypes unter ihnen) machen, aber wenn dies nicht oft gemacht wird und wir irgendeine Art von Unterstützung oder TH bekommen, denke ich nicht, dass es sich lohnt.
Wie von @ danidiaz erwähnt, verwendet dies im Wesentlichen die 'Applicative' und' Alternative' Instanzen für 'Compose Parser IO'. Man könnte diesen Typ entweder direkt verwenden oder ihn als weitere Anleitung zum Schreiben eines solchen Codes in größeren Entwicklungen verwenden. –