Я не знаю хорошей стратегии, чтобы добраться туда, где вы хотите быть, поскольку монада ParseJSON не является преобразователем или основана на IO. Что вам проще сделать, так это декодировать в один тип, а затем перевести во второй, как это было сделано в предыдущем вопросе 'Укажите значение по умолчанию для полей, недоступных в json с помощью aeson'.
Поскольку большие структуры могут быть громоздкими для воспроизведения, вы можете сделать структуру параметризованной и создать ее экземпляр с помощью IO Int
или Int
. Например, допустим, вам нужно поле a
из проводника, а b
как случайное из монады IO:
{-# 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
Обратите внимание, как loadExample
использует наш экземпляр traverse
для выполнения операций ввода-вывода внутри структуры. Вот пример использования:
Main> parseExample "{ \"a\" : 1111 }"
Just (Example {a = 1111, b = 5})
Дополнительно
Если у вас есть более одного типа поля, для которого вы хотите действие ввода-вывода, вы можете либо
Сделайте один тип данных для всех. Вместо b
типа IO Int
вы можете сделать его IO MyComplexRecord
. Это простое решение.
Более сложное и забавное решение — использовать параметр типа более высокого типа.
Для варианта 2 учтите:
data Example' f = Example { a :: Int
, b :: f Int
, c :: f String }
Затем вы можете использовать Proxy
и Control.Monad.Identity
вместо таких значений, как IO Int
и Int
, использовавшихся ранее. Вам нужно будет написать свой собственный обход, так как вы не можете получить Traverse
для этого класса (именно это дает нам mapM
, использованное выше). Мы могли бы создать класс обхода с типом (* -> *) -> *
, используя несколько расширений (среди них RankNTypes), но если это не делается часто, и мы не получаем какую-то поддержку вывода или TH, я не думаю, что это стоит того.
person
Thomas M. DuBuisson
schedule
19.10.2017