Простейший (ан)парсинг типов 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"
}
Проще простого.