Введение

Сериализация и десериализация типов 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 за нас автоматически.

Требования

  1. Собственно описание типа T.
  2. Реализация тайпкласса Generic для T (автоматически).
  3. Реализация тайпклассов ToJSON и FromJSON для T (автоматически).
  4. Функции из типа/в тип 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"
}

Проще простого.