使用 Aeson 读取编码为嵌套字符串的嵌套 JSON 数据

Reading nested JSON data encoded as a nested string with Aeson

我有这个奇怪的 JSON 来解析包含嵌套 JSON ... 的字符串。所以而不是

{\"title\": \"Lord of the rings\", \"author\": {\"666\": \"Tolkien\"}\"}"

我有

{\"title\": \"Lord of the rings\", \"author\": \"{\\"666\\": \\"Tolkien\\"}\"}"

这是我在 FromJSON 的实例中使用 decode 解析嵌套字符串的(失败的)尝试:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Data.Maybe
import GHC.Generics
import Data.Aeson
import qualified Data.Map as M

type Authors = M.Map Int String

data Book = Book
  {
    title :: String,
    author :: Authors
  }
  deriving (Show, Generic)

decodeAuthors x =  fromJust (decode x :: Maybe Authors)

instance FromJSON Book where
  parseJSON = withObject "Book" $ \v -> do
    t <- v .: "title"
    a <- decodeAuthors <?> v .: "author"
    return  $ Book t a

jsonTest = "{\"title\": \"Lord of the rings\", \"author\": \"{\\"666\\": \\"Tolkien\\"}\"}"

test = decode jsonTest :: Maybe Book

有没有办法一次性解码整个 JSON?谢谢!

这里有几个问题。

首先,你对<?>的使用是无意义的。我假设这是一个错字,而你的实际意思是 <$>.

第二个decodeAuthors的类型是ByteString -> Authors,也就是说它的参数是ByteString类型的,也就是说表达式v .: "author" 必须是 Parser ByteString 类型,这意味着必须有一个实例 FromJSON ByteString,但这样的实例不存在(出于我目前无法理解的原因)。

实际上想要的是v .: "author"到return一个Parser String(或者可能是Parser Text),然后让 decodeAuthors 接受 String 并将其转换为 ByteString(使用 pack),然后再传递给 decode:

import Data.ByteString.Lazy.Char8 (pack)

decodeAuthors :: String -> Authors
decodeAuthors x = fromJust (decode (pack x) :: Maybe Authors)

(另请注意:为您提供您认为它们应该具有的声明类型签名是个好主意。这可以让编译器更早地指出错误)


编辑:

正如@DanielWagner 正确指出的那样,pack 可能会混淆 Unicode 文本。如果要正确处理,使用utf8-string中的Data.ByteString.Lazy.UTF8.fromString进行转换:

import Data.ByteString.Lazy.UTF8 (fromString)

decodeAuthors :: String -> Authors
decodeAuthors x = fromJust (decode (fromString x) :: Maybe Authors)

但在那种情况下,您还应该注意 jsonTest 的类型:按照您的代码编写方式,其类型将是 ByteString,但任何非 ASCII 字符都可能是由于 IsString 的工作方式,inside 将被切断。要保留它们,您需要在其上使用相同的 fromString

jsonTest = fromString "{\"title\": \"Lord of the rings\", \"author\": \"{\\"666\\": \\"Tolkien\\"}\"}"