Условное добавление полей в вывод JSON

У меня есть пара типов, User и Post. Post создается User.

Моя база данных выглядит так же, как мои типы, которые

data User = { userID :: Integer, name :: String }
data Post = { content :: String, authorID :: Integer } -- authorID is the userID

Предполагая, что у меня есть действительные экземпляры To/FromJSON на основе этих типов, как мне условно включить значение в вывод моего JSON?

В качестве примера я могу просмотреть «профиль» пользователя, который будет включать все Post, созданные User. В других случаях я могу захотеть просмотреть различные User в системе, поэтому нет смысла возвращать Post, которые они сделали.

У меня нет проблем с получением Posts для данного User, и у меня нет проблем с получением User заданного Post. Я просто хочу знать, как лучше всего условно добавить ключ в мой JSON.

Кто-то упомянул мне, что я должен добавить поле к User для posts :: Maybe [Post] и добавить author :: Maybe User к моему типу Post. Хотя это будет работать для довольно маленьких типов, я чувствую, что некоторые типы с большим количеством отношений будут довольно большими и потенциально сложными в обслуживании.

Я также думал об использовании Map Text Data.Aeson.Value для создания/сериализации моей информации, но вы теряете большую часть (все?) Безопасность типов, делая что-то подобное.

ИЗМЕНИТЬ

Основываясь на приведенных выше типах данных, я определил следующие экземпляры ToJSON.

instance ToJSON User where
  toJSON (User i n) = object [ "id" .= i, "name" .= n ]

instance ToJSON Post where
  toJSON (Post c a) = object [ "content" .= c, "author_id" .= a ]

Это дало бы мне «простой» вывод json, что-то вроде User:

{
  "id": 4,
  "name": "User Name"
}

Post:

{
  "content": "This is the content of my post",
  "author_id": 4
}

Это может быть полезно, особенно при поиске предметов. Это позволяет отображать только ограниченный объем информации.

Я мог бы сделать что-то вроде

instance ToJSON User where
  toJSON (User i n) = object [ "id" .= i, "name" .= n, "posts" .= posts i ]

Что дало бы мне информацию о пользователях, а также все сообщения, связанные с этим пользователем, при условии допустимого определения posts. Это дало бы мне JSON, похожий на

{
  "id": 4,
  "name": "User Name",
  "posts": [
    {
      "content": "This is some content",
      "author_id": 4
    }
  ]
}

Я не понимаю, как я мог бы динамически устанавливать, какие поля я хотел бы отправить клиенту. Можно ли определить несколько экземпляров ToJSON и выбрать, какой экземпляр я хотел бы использовать для данного запроса? Или я должен просто сделать то, что было предложено ранее, и включить поле posts в мой тип User, который может содержать Maybe [Post]?


person Justin Wood    schedule 17.12.2015    source источник
comment
Я еще не уверен, что понял вопрос. Что затрудняет условное включение значения? Можете ли вы привести краткий пример, когда ваш JSON включает слишком много и, в идеале, также почему вы думаете, что трудно включить меньше?   -  person Daniel Wagner    schedule 18.12.2015
comment
@DanielWagner Я внес правку, надеюсь, это немного прояснит ситуацию.   -  person Justin Wood    schedule 18.12.2015
comment
Разве вы не можете просто... обойти класс типов? Я имею в виду, что вы можете написать сколько угодно функций типа Foo -> Value, они не должны называться toJSON.   -  person Daniel Wagner    schedule 18.12.2015


Ответы (1)


Я не думаю, что есть общий ответ на вопрос, следует ли вам использовать динамический JSON или придерживаться фиксированных экземпляров ToJSON, но вы можете легко смешать их и добавить дополнительные поля к значениям JSON, которые генерируются из ваших экземпляров.

Из того, что вы написали, я думаю, вы знаете, что Эсон представляет объект как Map Text Value? Для массивов он использует Vector Value.

Таким образом, вы можете вызвать toJSON в своем User и в списке Posts и просто вставить массив сообщений в качестве дополнительной записи на карту, если хотите:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.ByteString.Lazy.Char8 as B
import Data.Aeson
import Data.Aeson.Types
import qualified Data.HashMap.Strict as M

data User = User { userID :: Integer, name :: String }

data Post = Post { content :: String, authorID :: Integer }

instance ToJSON User where
    toJSON (User i n) = object [ "id" .= i, "name" .= n ]

instance ToJSON Post where
    toJSON (Post c a) = object [ "content" .= c, "author_id" .= a ]

babs = User { userID = 5, name = "babs" }

posts = [ Post { content = "blah", authorID = 5 } ]

userWithPosts = Object (M.insert "posts" v o)
  where
    Object o = toJSON babs
    v        = toJSON posts

main = B.putStrLn (encode userWithPosts)

Вызов encode дает вам комбинированный JSON:

*Main B> main
{"name":"babs","id":5,"posts":[{"author_id":5,"content":"blah"}]}
person Shaun the Sheep    schedule 18.12.2015