Haskell стал полезным в качестве веб-языка (спасибо Servant
!), И все же JSON по-прежнему настолько болезнен для меня, что я, должно быть, делаю что-то не так (?)
Я слышал, что JSON упоминается какДостаточно больно, и ответы, которые я слышал, вращаются вокруг «используйте PureScript», «дождитесь суб / строкового набора», «используйте эзотерику, как винил», «Aeson + просто справляется со взрывом типов данных котельной пластины».
Как (несправедливый) ориентир, я действительно наслаждаюсь легкостью JSON-истории Clojure (конечно, это динамический язык, и у него есть свои компромиссы, из-за которых я все еще предпочитаю Haskell).
Вот пример, на который я смотрел в течение часа.
{
"access_token": "xxx",
"batch": [
{"method":"GET", "name":"oldmsg", "relative_url": "<MESSAGE-ID>?fields=from,message,id"},
{"method":"GET", "name":"imp", "relative_url": "{result=oldmsg:$.from.id}?fields=impersonate_token"},
{"method":"POST", "name":"newmsg", "relative_url": "<GROUP-ID>/feed?access_token={result=imp:$.impersonate_token}", "body":"message={result=oldmsg:$.message}"},
{"method":"POST", "name":"oldcomment", "relative_url": "{result=oldmsg:$.id}/comments", "body":"message=Post moved to https://workplace.facebook.com/{result=newmsg:$.id}"},
{"method":"POST", "name":"newcomment", "relative_url": "{result=newmsg:$.id}/comments", "body":"message=Post moved from https://workplace.facebook.com/{result=oldmsg:$.id}"},
]
}
Мне нужно отправить это на рабочее место FB, которое скопирует сообщение в новую группу и прокомментирует ссылку на обоих,связывание друг с другом.
Моя первая попытка выглядела примерно так:
data BatchReq = BatchReq {
method :: Text
, name :: Text
, relativeUrl :: Text
, body :: Maybe Text
}
data BatchReqs = BatchReqs {
accessToken :: Text
, batch :: [BatchReq]
}
softMove tok msgId= BatchReqs tok [
BatchReq "GET" "oldmsg" (msgId `append` "?fields=from,message,id") Nothing
...
]
Это ужасно жестко, и иметь дело со Maybe
повсюду неудобно.Nothing
- это JSON null
?Или поле должно отсутствовать?Затем я беспокоился о получении экземпляров Aeson и должен был выяснить, как конвертировать, например, relativeUrl
в relative_url
.Затем я добавил конечную точку, и теперь у меня есть конфликт имен.DuplicateRecordFields
!Но подождите, это вызывает так много проблем в других местах.Поэтому обновите тип данных, чтобы использовать, например, batchReqRelativeUrl
, и удалите его при получении экземпляров, используя Typeable
s и Proxy
s.Затем мне нужно было добавить конечные точки и / или поменять форму этого жесткого типа данных, для которого я добавил больше точек данных, стараясь не допустить, чтобы «тирания небольших различий» слишком раздула мои типы данных.
При этомЯ был в основном потребляющим JSON, поэтому решил, что "динамическим" будет использование lens
es.Итак, чтобы развернуть поле JSON, содержащее идентификатор группы, я сделал:
filteredBy :: (Choice p, Applicative f) => (a -> Bool) -> Getting (Data.Monoid.First a) s a -> Optic' p f s s
filteredBy cond lens = filtered (\x -> maybe False cond (x ^? lens))
-- the group to which to move the message
groupId :: AsValue s => s -> AppM Text
groupId json = maybe (error500 "couldn't find group id in json.")
pure (json ^? l)
where l = changeValue . key "message_tags" . values . filteredBy (== "group") (key "type") . key "id" . _String
Это довольно тяжело для доступа к полям.Но мне также нужно генерировать полезные нагрузки, и я не достаточно опытен, чтобы увидеть, как линзы будут хороши для этого.Обращаясь к мотивирующему пакетному запросу, я придумал «динамический» способ написания этих полезных нагрузок.Это можно упростить с помощью помощника fns, но я даже не уверен, насколько лучше это получится.
softMove :: Text -> Text -> Text -> Value
softMove accessToken msgId groupId = object [
"access_token" .= accessToken
, "batch" .= [
object ["method" .= String "GET", "name" .= String "oldmsg", "relative_url" .= String (msgId `append` "?fields=from,message,id")]
, object ["method" .= String "GET", "name" .= String "imp", "relative_url" .= String "{result=oldmsg:$.from.id}?fields=impersonate_token"]
, object ["method" .= String "POST", "name" .= String "newmsg", "relative_url" .= String (groupId `append` "/feed?access_token={result=imp:$.impersonate_token}"), "body" .= String "message={result=oldmsg:$.message}"]
, object ["method" .= String "POST", "name" .= String "oldcomment", "relative_url" .= String "{result=oldmsg:$.id}/comments", "body" .= String "message=Post moved to https://workplace.facebook.com/{result=newmsg:$.id}"]
, object ["method" .= String "POST", "name" .= String "newcomment", "relative_url" .= String "{result=newmsg:$.id}/comments", "body" .= String "message=Post moved from https://workplace.facebook.com/{result=oldmsg:$.id}"]
]
]
Я рассматриваю возможность иметь в коде BLOB-объекты JSON или читать их как файлыи используя Text.Printf
для объединения в переменные ...
Я имею в виду, я могу сделать все это так, но, безусловно, хотел бы найти альтернативу.API FB немного уникален тем, что его нельзя представить в виде жесткой структуры данных, как многие API-интерфейсы REST;они называют его своим API-интерфейсом Graph, который более динамичен в использовании, и рассматривать его как жесткий API до сих пор было болезненно.
(Кроме того, благодаря всему сообществу, помогшему мне продвинуться так далеко с Haskell!)