Purescript重用Argonaut JSON解码为Affjax响应

时间:2017-03-21 12:57:01

标签: purescript argonaut

我正在尝试从Haskell服务器获取一些JSON数据,但我遇到了Respondeable实例,以及一般的Affjax。我已经使用Data.Argonaut.Generic.Aeson(GA)定义了EncodeJson + DecodeJson,但我无法弄清楚如何将其与Respondeable实例相匹配,并且它来自响应函数。

它给出了错误“无法匹配类型外部类型与Json”但是可以重用我的decodeJson实例而无需手动创建任何其他内容吗?也许通过创建一个IsForeign实例,但使用GA.decodeJson?我只是不确定如何去做。我已经看到它是如何在https://github.com/purescript/purescript-foreign/blob/master/examples/Complex.purs手动完成的,但我有复杂的类型需要与我的Haskell JSON输出相匹配,手动操作会非常痛苦。

我正在使用purescript 10.7,Affjax 3.02和argonaut 2.0.0,以及argonaut-generic-codecs 5.1.0。谢谢!

testAffjax :: forall eff. Aff (ajax :: AJAX | eff) (Answer)
testAffjax = launchAff do
  res <- affjax $ defaultRequest { url = "/", method = Left GET }
  pure res.response


data Answer = Answer {
  _answer :: String
, _isCorrect :: Boolean
, _hint :: String
}

{- PROBLEM -}
instance respondableAnswer :: Respondable Answer where
  responseType = Tuple Nothing JSONResponse
  fromResponse = GA.decodeJson {- Error here -}

derive instance genericAnswer :: Generic Answer
instance showAnswer :: Show Answer where
  show = gShow
instance encodeAnswer :: EncodeJson Answer where
  encodeJson = GA.encodeJson
instance decodeAnswer :: DecodeJson Answer where
  decodeJson = GA.decodeJson

1 个答案:

答案 0 :(得分:7)

您正在寻找的是一个适应JSON解码器的功能:

decodeJson :: forall a. Json -> Either String a

使用F而不是Either返回。 FData.Foreign Except MultipleErrors a中定义的同义词。要做到这一点,我们需要:

  1. String错误翻译为MultipleErrors
  2. Either转换为Except
  3. MultipleErrorsData.Foreign中定义的另一个同义词,这次是NonEmptyList ForeignErrorLooking at ForeignError还有一个名为ForeignError的构造函数,它允许我们提供一些字符串消息。这使我们需要创建一个NonEmptyList,这很简单:

    remapError = pure <<< ForeignError
    

    NonEmptyListApplicative,因此我们可以使用pure创建一个单元素列表。

    Either转到Except也很简单。再次looking at the definitions in Pursuit我们可以看到:

    newtype ExceptT m e a = ExceptT (m (Either e a))
    type Except = ExceptT Identity
    

    所以ExceptT已经只是一个花哨的Either,给了我们:

    eitherToExcept = ExceptT <<< pure
    

    此处pureEither e a提升为m (Either e a) Except m ~ Identity

    所以现在我们可以采用这些东西,并为Affjax响应制作一个通用的解码JSON&#34;功能:

    decodeJsonResponse :: forall a. DecodeJson a => Json -> F a
    decodeJsonResponse =
      ExceptT <<< pure <<< lmap (pure <<< ForeignError) <<< decodeJson
    

    此处发生的唯一其他事情是我们使用lmap映射Either的左侧部分,以执行错误消息类型转换位。

    我们现在可以使用Kleisli合成((<=<))将此decodeJsonResponse与原始fromResponse链接起来ResponseContent -> F Json

    instance respondableAnswer :: Respondable Answer where
      responseType = Tuple (Just applicationJSON) JSONResponse
      fromResponse = decodeJsonResponse <=< fromResponse
    

    以下是使用Answer类型的完整示例:

    module Main where
    
    import Prelude
    
    import Control.Monad.Aff (Aff)
    import Control.Monad.Except (ExceptT(..))
    
    import Data.Argonaut (class DecodeJson, class EncodeJson, Json, decodeJson)
    import Data.Argonaut.Generic.Argonaut as GA
    import Data.Bifunctor (lmap)
    import Data.Foreign (F, ForeignError(..))
    import Data.Generic (class Generic, gShow)
    import Data.Maybe (Maybe(..))
    import Data.MediaType.Common as MediaType
    import Data.Tuple (Tuple(..))
    
    import Network.HTTP.Affjax as AX
    import Network.HTTP.Affjax.Response as AXR
    
    testAffjax :: forall eff. Aff (ajax :: AX.AJAX | eff) Answer
    testAffjax = _.response <$> AX.get "/"
    
    newtype Answer = Answer
      { _answer :: String
      , _isCorrect :: Boolean
      , _hint :: String
      }
    
    derive instance genericAnswer :: Generic Answer
    
    instance showAnswer :: Show Answer where
      show = gShow
    
    instance encodeAnswer :: EncodeJson Answer where
      encodeJson = GA.encodeJson
    
    instance decodeAnswer :: DecodeJson Answer where
      decodeJson = GA.decodeJson
    
    instance respondableAnswer :: AXR.Respondable Answer where
      responseType = Tuple (Just MediaType.applicationJSON) AXR.JSONResponse
      fromResponse = decodeJsonResponse <=< AXR.fromResponse
    
    decodeJsonResponse :: forall a. DecodeJson a => Json -> F a
    decodeJsonResponse =
      ExceptT <<< pure <<< lmap (pure <<< ForeignError) <<< decodeJson