haskell - 尝试使用 Aeson 解码时出现莫名其妙的错误

标签 haskell decoder aeson

我已经在 Aeson 解码问题上苦苦挣扎了一段时间了。简而言之,当在应用程序的上下文中使用时,如下面第 (6) 行所示,解码器失败,给出错误

 src/CFUpload.hs:(66,6)-(71,27): Non-exhaustive patterns in function parseJSON

我已在解码器 instance FromJSON CFUploadResponse 中指出了下面的这些行。 然而,当解码器应用于 repl 中的数据时,我认为它是 接收,成功(见标记(***)的讨论)。

现在是血淋淋的细节:首先是抛出错误的代码, 然后是类型,然后是解码器和讨论。

代码。

    post "/image" $ do
      image <- jsonData :: ActionM CFImage.CFImage  -- (1)
      liftIO $ CFImage.downloadImage image  -- (2)
      cfImageUploadUrl <- liftIO Image.requestCFToken  -- (3)
      let filename = CFImage.getFilenameFromImage image  -- (4)   
      cfUploadedImageResponse <- liftIO $ Image.uploadTheImage cfImageUploadUrl filename  -- (5)
      let cfUploadedImageResponse' = Data.Aeson.decode $ BL.pack cfUploadedImageResponse :: Maybe CFUpload.CFUploadResponse   -- (6)
      text $ pack $ show cfUploadedImageResponse'   -- (7)

这是 Scotty 服务器应用程序的一部分。我正在使用 Postman 测试代码。第 (5) 行一切正常:服务器接受 POSTed 数据,其中包含图像 URL 和图像文件名。第 (2) 行使用该数据将图像下载到目录 cf-image 中的文件中。 。第 (3) 行向 Cloudflare 请求一次性图像上传 URL。第 (4) 行提取文件名,并在第 (5) 行使用该文件名将图像 POST 到 cloudflare,返回包含指向 Cloudflare 服务器上图像的 URL 的数据。我知道这个请求成功了,因为我已经短路了上面的代码,将第 (6) 和 (7) 行替换为

text $ pack $ cfUploadedImageResponse

响应是

"{\n  \"result\": {\n    \"id\": \"673996fb-4d26-4332-6e6b-e8bf7b608500\",\n    \"filename\": \"bird2.jpg\",\n    \"uploaded\": \"2023-03-18T22:53:56.705Z\",\n    \"requireSignedURLs\": false,\n    \"variants\": [\n      \"https://imagedelivery.net/9U-0Y4sEzXlO6BXzTnQnYQ/673996fb-4d26-4332-6e6b-e8bf7b608500/public\"\n    ]\n  },\n  \"success\": true,\n  \"errors\": [],\n  \"messages\": []\n}"
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             

将此字符串称为 testResponse 。如果喂testResponse对于Python的打印,你会得到

{
  "result": {
    "id": "673996fb-4d26-4332-6e6b-e8bf7b608500",
    "filename": "bird2.jpg",
    "uploaded": "2023-03-18T22:53:56.705Z",
    "requireSignedURLs": false,
    "variants": [
         "https://imagedelivery.net/9U-0Y4sEzXlO6BXzTnQnYQ/673996fb-4d26-4332-6e6b-e8bf7b608500/public"
        ]
      },
    "success": true,
    "errors": [],
    "messages": []
}

类型。

这个字符串,我们称之为 testResponsePretty ,据我所知,是正确的:与以下类型进行比较:

data CFUploadResponse = CFUploadResponse
  {
    result   :: CFUploadResult,
    success  :: Bool,
    errors   :: [String],
    messages :: [String]
  } deriving Show

data CFUploadResult = CFUploadResult {
    id                :: String,
    filename          :: String,
    uploaded          :: String,
    requireSignedURLs :: Bool,
    variants          :: [String]
  } deriving Show

解码器。

这是解码器:

instance FromJSON CFUploadResponse where
     parseJSON (Object v) =           -- (66)
        CFUploadResponse     <$>      -- (67) 
            v .: "result"    <*>      -- (68)
            v .: "success"   <*>      -- (69)
            v .: "errors"    <*>      -- (70)
            v .: "messages"           -- (71)

-- Tell Aeson how to convert a CFUploadResponse object to a JSON string.

instance FromJSON CFUploadResult where
    parseJSON = withObject "CFUploadResult" $ \o -> do
      id <- o .: Data.Text.pack  "id"
      filename <- o .: Data.Text.pack "filename"
      uploaded <- o .:  Data.Text.pack "uploaded"
      requireSignedURLs <- o .: Data.Text.pack "requireSignedURLs"
      variants <- o .: Data.Text.pack  "variants"
      return (CFUploadResult id filename uploaded requireSignedURLs variants) 
  

讨论。 (***)
尤其令人困惑的是以下内容。让testResponse如上并令

myDecode str = Data.Aeson.eitherDecode $ BL.pack str :: Either String (Maybe CFUpload.CFUploadResponse)

然后这样做:

$ stack repl

ghci> myDecode testResponse

结果是

Right (Just (CFUploadResponse {result = CFUploadResult {id = "49660d63-a43f-4011-1a7a-ff6435305d00", filename = "bird2.jpg", uploaded = "2023-03-16T23:08:22.768Z", requireSignedURLs = False, variants = ["https://imagedelivery.net/9U-0Y4sEzXlO6BXzTnQnYQ/49660d63-a43f-4011-1a7a-ff6435305d00/public"]}, success = True, errors = [], messages = []}))

最佳答案

当您将第 (6) 行和第 (7) 行替换为 text $ pack $cfUploadedImageResponse 时,来自 Web 服务器的响应字节流应为:

{
  "result":...
  ...
}

不是:

"{\n  \"result\":...

也就是说,响应应该以左大括号开头,而不是双引号。

我猜测您的 uploadTheImage 函数返回的是 show response 的结果,而不是响应字符串本身。如果 cfUploadedImageResponse 的实际值是一个字符串,其第一个字符是双引号,那么您要求 Aeson 解码恰好是单个字符串的 JSON 值(其内容是一些 JSON,但这与解码器无关)。这将导致您的实例中的模式匹配:

parseJSON (Object v)

失败,因为v::Values是一个String而不是Object

关于haskell - 尝试使用 Aeson 解码时出现莫名其妙的错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75779314/

相关文章:

haskell - 从包装器中动态匹配嵌套的 GADT

http - MPlayer 如何识别 MJPEG 流?

ffmpeg - 用于在 C 项目中解码 x265 (HEVC) 流的免费和开源库?

json - 具有错误处理功能的 Aeson 和 Lenses

Haskell:类型推断和函数组合

function - Haskell:替代 f = foo e, e = bar d, d = baz c 的模式

C++ MP3、WAV 和 AAC(低级)解码器?

haskell - 我想使用酸存储 aeson 的值类型

haskell - 如何使用 Template Haskell、Aeson 和类型系列自动派生 FromJSON

haskell - 什么时候应该优先使用 TypeApplications 而不是 simple::signatures 或 ScopedTypeVariables?