ПРИМЕЧАНИЕ : в приведенных ниже примерах кода я использовал файл "moves.json"
, содержимое которого является вашим минимальным примером выше. За исключением getMoves
, который может анализировать любой действительный JSON, другие примеры кода не будут работать с файлом "moves.json"
, полученным из связанного файла "moves.js"
, поскольку формат отличается (например, это объект, а не массив, с одной стороны).
Самый простой способ использования Aeson для разбора произвольного JSON - это синтаксический разбор в Value
:
import Data.Aeson
import Data.Maybe
import qualified Data.ByteString.Lazy as B
getMoves :: IO Value
getMoves = do
mv <- decode <$> B.readFile "moves.json"
case mv of
Nothing -> error "invalid JSON"
Just v -> return v
Любое допустимое JSON может быть проанализирован таким образом, и результирующая Value
имеет полностью динамическую c структуру, которую можно программно проверять во время выполнения. Здесь могут быть полезны библиотека объектива и монада Maybe
. Например, чтобы найти (первый) объект с отсутствующим secondary.chance
из 100, вы можете использовать:
{-# LANGUAGE OverloadedStrings #-}
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import qualified Data.Vector as Vector
import qualified Data.ByteString.Lazy as B
find100 :: Value -> Maybe Value
find100 inp = do
arr <- inp ^? _Array
Vector.find (\s -> s ^? key "secondary" . key "chance" . _Integer == Just 100) arr
test1 = find100 <$> getMoves
, который выдает:
> test1
Just (Object (fromList [("secondary",Object (fromList [("chance",Number 100.0),
("self",Object (fromList []))]))]))
, который является Value
представление объекта:
{
"secondary": {
"chance": 100,
"self": {}
}
}
Если вы хотите, чтобы результирующий проанализированный объект имел большую структуру, то вам нужно начать с выяснения представления Haskell, которое будет работать со всеми возможными объектами, которые вы ' пере разбираюсь. Для вашего примера разумное представление может быть:
type Moves = [Move]
data Move = Move
{ secondary :: Secondary'
} deriving (Show, Generic)
newtype Secondary' = Secondary' (Maybe Secondary) -- Nothing if json is "false"
deriving (Show, Generic)
data Secondary = Secondary
{ chance :: Maybe Int
, boosts :: Maybe Boosts
, volatileStatus :: Maybe String
, self :: Maybe Self
} deriving (Show, Generic)
data Self = Self
{ boosts :: Maybe Boosts
} deriving (Show, Generic)
newtype Boosts = Boosts (HashMap.HashMap Text.Text Int)
deriving (Show, Generic)
Это предполагает, что все ходы имеют поле secondary
, которое является либо "false"
, либо объектом. Предполагается также, что возможно множество клавиш повышения, поэтому удобнее представлять их в виде произвольных текстовых строк в хэш-карте Boosts
. Кроме того, это обрабатывает наличие "boosts"
непосредственно в "secondary"
или вложение в "self"
, поскольку в вашем примере приведены примеры обеих форм, хотя, возможно, это была ошибка.
Для этих типов данных по умолчанию все экземпляры для Move
, Self
и Secondary
могут быть использованы:
instance FromJSON Move
instance FromJSON Self
instance FromJSON Secondary
Оболочка нового типа Secondary'
вокруг Secondary
затем используется для обработки false
по сравнению с объектом, использующим пользовательский экземпляр:
instance FromJSON Secondary' where
parseJSON (Bool False) = pure $ Secondary' Nothing
parseJSON o = Secondary' . Just <$> parseJSON o
Пользовательский экземпляр также необходим для Boosts
, чтобы проанализировать его в соответствующую хэш-карту:
instance FromJSON Boosts where
parseJSON = withObject "Boosts" $ \o -> Boosts <$> mapM parseJSON o
Теперь со следующим драйвером:
test2 :: IO (Either String Moves)
test2 = eitherDecode <$> B.readFile "moves.json"
это декодирует ваш пример следующим образом:
> test2
Right [Move {secondary = Secondary' Nothing},Move {secondary =
Secondary' (Just (Secondary {chance = Just 10, boosts = Just (Boosts
(fromList [("spd",-1)])), volatileStatus = Nothing, self =
...
Используя eitherDecode
выше, мы можем получить сообщение об ошибке, если синтаксический анализ не удался. Например, если вы запустите это на "moves.json"
, полученном из "moves.js"
, вместо этого вы получите:
> test2
Left "Error in $: parsing [] failed, expected Array, but encountered Object"
, когда анализатор заметит, что пытается проанализировать массив [Move]
, но вместо этого находит объект, обозначенный именами перемещений Pokemon.
Вот полный код, показывающий оба типа анализа:
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import GHC.Generics
import qualified Data.Text as Text
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Vector as Vector
import qualified Data.ByteString.Lazy as B
--
-- Parse into a dynamic Value representation
getMoves :: IO Value
getMoves = do
mv <- decode <$> B.readFile "moves.json"
case mv of
Nothing -> error "invalid JSON"
Just v -> return v
find100 :: Value -> Maybe Value
find100 inp = do
arr <- inp ^? _Array
Vector.find (\s -> s ^? key "secondary" . key "chance" . _Integer == Just 100) arr
test1 :: IO (Maybe Value)
test1 = find100 <$> getMoves
--
-- Parse into suitable static data structures
-- whole file is array of moves
type Moves = [Move]
data Move = Move
{ secondary :: Secondary'
} deriving (Show, Generic)
newtype Secondary' = Secondary' (Maybe Secondary) -- Nothing if json is "false"
deriving (Show, Generic)
data Secondary = Secondary
{ chance :: Maybe Int
, boosts :: Maybe Boosts
, volatileStatus :: Maybe String
, self :: Maybe Self
} deriving (Show, Generic)
data Self = Self
{ boosts :: Maybe Boosts
} deriving (Show, Generic)
newtype Boosts = Boosts (HashMap.HashMap Text.Text Int)
deriving (Show, Generic)
instance FromJSON Move
instance FromJSON Self
instance FromJSON Secondary
instance FromJSON Secondary' where
parseJSON (Bool False) = pure $ Secondary' Nothing
parseJSON o = Secondary' . Just <$> parseJSON o
instance FromJSON Boosts where
parseJSON = withObject "Boosts" $ \o -> Boosts <$> mapM parseJSON o
test2 :: IO (Either String Moves)
test2 = eitherDecode <$> B.readFile "moves.json"