Простейший (ан)парсинг типов Haskell в JSON c Data.Aeson
Введение⌗
Сериализация и десериализация типов Haskell в популярный формат JSON проста как пробка с
пакетом Aeson
(Data.Aeson).
Положим, что T - тип данных, который мы хотим сохранять в и считывать из формата JSON.
Для этого нам нужны стрелки (изоморфизм) между типами T и ByteString, строками,
содержащими все корректные JSON-репрезентации значений нашего T.
Пакет Data.Aeson содержит высокоэффективные функции работы с форматом JSON. Среди прочих
имеем функции кодирования и декодирования между JSON и типами Haskell:
encode :: ToJSON a => a -> ByteString
decode :: FromJSON a => ByteString -> Maybe a
Для успешного преобразования нам нужно сделать наш тип T экземпляром класса типов
ToJSON (сохранение в JSON) и FromJSON (считывание из JSON). Парсер и анпарсер можно
написать самому, но лучше всего использовать расширение языка DeriveGeneric и попросить
компилятор Haskell построить реализации ToJSON и FromJSON за нас автоматически.
Требования⌗
- Собственно описание типа
T. - Реализация тайпкласса
GenericдляT(автоматически). - Реализация тайпклассов
ToJSONиFromJSONдляT(автоматически). - Функции из типа/в тип
T, использующиеencodeиdecode.
Пример⌗
Вот простейший пример “плоского” типа Scenario, храняшего сценарий компьютерной игры в
файле в формате JSON (все вложенные типы, в свою очередь, тоже должны реализовывать
Generic и FromJSON / ToJSON):
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data Scenario = Scenario
{ scenarioName :: String
, scenarioDescription :: String
, scenarioTerrainFile :: FilePath
, scenarioBlueOrbatFile :: FilePath
, scenarioRedOrbatFile :: FilePath
, scenarioOverlayFile :: FilePath
, scenarioGoals :: Bool
}
deriving (Show, Eq, Generic)
instance FromJSON Scenario
instance ToJSON Scenario
А вот функции чтения/записи сценария в файл:
scenarioFromFileIO :: FilePath -> IO (Maybe Scenario)
scenarioFromFileIO f = do
bs <- B.readFile f
return $ decode bs
scenarioToFileIO :: FilePath -> Scenario -> IO ()
scenarioToFileIO f t = do
let t' = encode t
B.writeFile f t'
Результатом успешного парсинга файла будет Just scen со считанной в scen
репрезентацией. При неуспешном парсинге имеем Nothing.
Вот так выглядит JSON-файл сценария:
{
"scenarioBlueOrbatFile":"~/projects/haskell/ld/executable/Orbats/TestOrbatBlue.json",
"scenarioOverlayFile":"~/projects/haskell/ld/executable/Overlays/TestOverlay.jpg",
"scenarioGoals":false,
"scenarioName":"A test scenario",
"scenarioTerrainFile":"~/projects/haskell/ld/executable/Terrains/TestTerrain.json",
"scenarioDescription":"This is only a test scenario with test terrain and orbat",
"scenarioRedOrbatFile":"~/projects/haskell/ld/executable/Orbats/TestOrbatRed.json"
}
Проще простого.