Haskell 如何使用Aeson解析分布在数组中的值?

Haskell 如何使用Aeson解析分布在数组中的值?,haskell,aeson,Haskell,Aeson,我的json值为: { "name": "xyz1", "extra": [ { "this_string_A": "Hello" }, { "this_string_B": "World" } ] } 以及以下数据类型: data Abc = Abc { name :: String

我的json值为:

{
  "name": "xyz1",
  "extra": [
    {
      "this_string_A": "Hello"
    },
    {
      "this_string_B": "World"
    }
  ]
}
以及以下数据类型:

data Abc = Abc
  { name :: String 
  , a :: Maybe String
  , b :: Maybe String
  } deriving (Generic, Show)
在上面的例子中,我希望它解析为Abc“xyz1”(只是“Hello”)(只是“World”)

我不知道如何在aeson
解析器
上下文中有条件地解析
extra
(它是一个JSON数组)中的值。如何获取额外[0]的
。例如,此字符串\u a
?我

我的尝试:

我想我可以创建自己的
解析器(可能是字符串)
函数,但遇到了令人困惑的错误:

instance FromJSON Abc where
     parseJSON = withObject "Abc" $ \v -> Abc
         <$> v .: "name"
         <*> myParse v
         <*> myParse v

myParse :: Object -> Parser (Maybe String)
myParse x =  withArray "extra" myParse2 (x)

myParse2 :: Array -> Parser (Maybe String)
myParse2 = undefined

如果我将
x
替换为
Object x
,则得到解析错误:

Left "Error in $: parsing extra failed, expected Array, but encountered Object" 

完整示例(运行
test
函数进行测试):

{-#派生通用语言}
{-#语言重载字符串}
模块示例,其中
进口GHC.仿制药
导入数据.Aeson
导入Data.Aeson.Types
数据Abc=Abc
{name::String
,a::可能是字符串
可能是字符串
}派生(通用、显示)
来自JSON Abc的实例,其中
parseJSON=withObject“Abc”$\v->Abc
v.:“姓名”
(v.:“extra”)--查找对象在何处具有此字符串的键??
(v.:“extra”)--查找对象在何处具有此字符串的键??
测试::字符串Abc或
测试=解码示例JSON
exampleJson=“{\'name\':\'xyz1\',\'extra\':[{\'this\u string\u A\':\'Hello\',{\'this\u string\u B\':\'World\'}”
部分答案

instance FromJSON Abc where
     parseJSON = withObject "Abc" $ \v -> Abc
         <$> v .: "name"
         <*> (v .: "extra" >>= myParse)
         <*> (v .: "extra" >>= myParse)


myParse :: Array -> Parser (Maybe String)
myParse x = withArray "extra" (lookupDictArray "this_string_a") (Array x)

lookupDictArray :: Text -> Array -> Parser (Maybe String)
lookupDictArray k a = do
  let v = Vector.find (maybe False (HashMap.member k) . parseMaybe parseJSON) a
  case v of
    Just v' -> withObject "grrrrrrrrrrr" (\v -> v .: k) v'
    Nothing -> pure Nothing
xxx的
“helpers”让一切都有点尴尬,但这就是原因

Aeson
解析器
类型命名错误,这会导致混淆。 Aeson
Parser
对象的思想是它们表示一元解析结果。(这不同于您在Parsec等中找到的表示实际一元语法分析器的
语法分析器
对象。)因此,您应该将
语法分析器a
视为与
或ParseError a
同构的一元结果,这是一种可能失败的结果

这些解析结果通常以应用程序的方式组合在一起。因此,如果您有一个类似以下的解析器:

data Xyz = Xyz { x :: String, y :: String }
instance FromJSON Xyz where
  parseJSON = withObject "Xyz" $ \v ->
    Xyz <$> v .: "x" <*> v .: "y"
现在,函数
parseJSON
的类型是
Value->Parser a
。这应该被恰当地称为解析器,但为了避免混淆,我们将其称为“parse function”。解析函数接受JSON表示(一个
,或一个
对象
或其他一些JSON内容)并返回解析结果。
withXXX
函数族用于在JSON内容之间调整解析函数。如果您有一个需要
对象的解析函数,例如:

\v -> Xyz <$> v .: "x" <*> v .: "y"   :: Object -> Parser Xyz
您希望
extra
成为一个
对象
,并希望使用适当的
和xxx
帮助程序将其从整个JSON对象
v::Object
中提取出来,以将解析函数从一种输入JSON类型调整到另一种输入JSON类型。那么,让我们编写一个一元函数(实际上是一个解析函数)来实现这一点:

getExtra :: Object -> Parser Object
getExtra v = do
首先,我们从
v
中提取可选的“额外”组件。我们在这里使用条件形式,因此
mextra::Maybe Value

  mextra <- v .:? "extra"
  case mextra of
    Just vv -> vv & withArray "Abc.extra" (\arr -> do
现在,
arr
是一个
数组=向量值
。我们希望它是一个
对象的数组。让我们将
值作为列表拉出:

      let vallst = toList arr
然后在
withObject
的帮助下,一元遍历列表,以确保它们都是预期的
Object
s。请注意此处使用的
pure
,因为我们希望提取
对象
s,而无需任何额外处理:

      objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
对于
Nothing
案例(“extra”未找到),我们只返回一个空hashmap:

    Nothing -> return HashMap.empty
完整函数如下所示:

getExtra :: Object -> Parser Object
getExtra v = do
  mextra <- v .:? "extra"
  case mextra of
    Just vv -> vv & withArray "Abc.extra" (\arr -> do
      let vallst = toList arr
      objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
      return $ HashMap.unions objlst)
    Nothing -> return HashMap.empty
它是这样工作的:

λ> main
Right (Abc {name = "xyz1", a = Just "Hello", b = Just "World"})
完整代码:

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

import Data.Aeson (eitherDecode, FromJSON, Object, parseJSON, withArray, withObject, (.:), (.:?))
import Data.Aeson.Types (Parser)
import GHC.Generics (Generic)
import qualified Data.ByteString.Lazy as BL (ByteString)
import qualified Data.HashMap.Strict as HashMap (empty, unions)
import Data.Function ((&))
import Data.Foldable (toList)

data Abc = Abc
  { name :: String
  , a :: Maybe String
  , b :: Maybe String
  } deriving (Generic, Show)

instance FromJSON Abc where
  parseJSON =
   withObject "Abc" $ \v -> do
    extra <- getExtra v
    Abc <$> v .: "name" <*> extra .:? "this_string_A" <*> extra .:? "this_string_B"

getExtra :: Object -> Parser Object
getExtra v = do
  mextra <- v .:? "extra"
  case mextra of
    Just vv -> vv & withArray "Abc.extra" (\arr -> do
      let vallst = toList arr
      objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
      return $ HashMap.unions objlst)
    Nothing -> return HashMap.empty

example :: BL.ByteString
example = "{\"name\": \"xyz1\", \"extra\": [{\"this_string_A\": \"Hello\"}, {\"this_string_B\": \"World\"}]}"

main = print (eitherDecode example :: Either String Abc)
{-#派生通用语言}
{-#语言重载字符串}
导入Data.Aeson(eitherDecode、FromJSON、Object、parseJSON、withArray、withObject、(.:)、(.:?)
导入Data.Aeson.Types(解析器)
进口GHC.仿制药(仿制药)
将限定数据.ByteString.Lazy导入为BL(ByteString)
将限定的Data.HashMap.Strict导入为HashMap(空,联合)
导入数据。函数(&))
导入数据。可折叠(toList)
数据Abc=Abc
{name::String
,a::可能是字符串
可能是字符串
}派生(通用、显示)
来自JSON Abc的实例,其中
parseJSON=
withObject“Abc”$\v->do
额外解析器对象
getExtra v=do
mextra vv&带有数组“Abc.extra”(\arr->do)
设vallst=toList arr
objlst返回HashMap.empty
示例::BL.ByteString
示例=“{\'name\':\'xyz1\',\'extra\':[{\'this\u string\u A\':\'Hello\'},{\'this\u string\u B\':\'World\'}”
main=print(解码示例::任意字符串Abc)

是否可以选择将
extra
建模为求和类型,而不是两个
Maybe
值?两个
Maybe
值似乎很奇怪,因为这种设计允许四种状态,包括两者都是
和都是
。您能给出两个其他输入示例吗?不清楚(至少对我来说)json中可能存在什么样的变化。例如,
extra
是否始终存在?是否
extra
始终包含两个元素?是否
extra
的元素始终只有一个键/值对?键是否始终是
此字符串A
此字符串B
或其他键遇到?我不担心变化。如果我能让它与提供的输入一起工作,那就足够了。
      objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
      return $ HashMap.unions objlst)
    Nothing -> return HashMap.empty
getExtra :: Object -> Parser Object
getExtra v = do
  mextra <- v .:? "extra"
  case mextra of
    Just vv -> vv & withArray "Abc.extra" (\arr -> do
      let vallst = toList arr
      objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
      return $ HashMap.unions objlst)
    Nothing -> return HashMap.empty
instance FromJSON Abc where
  parseJSON =
   withObject "Abc" $ \v -> do
    extra <- getExtra v
    Abc <$> v .: "name" <*> extra .:? "this_string_A" <*> extra .:? "this_string_B"
example :: BL.ByteString
example = "{\"name\": \"xyz1\", \"extra\": [{\"this_string_A\": \"Hello\"}, {\"this_string_B\": \"World\"}]}"

main = print (eitherDecode example :: Either String Abc)
λ> main
Right (Abc {name = "xyz1", a = Just "Hello", b = Just "World"})
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson (eitherDecode, FromJSON, Object, parseJSON, withArray, withObject, (.:), (.:?))
import Data.Aeson.Types (Parser)
import GHC.Generics (Generic)
import qualified Data.ByteString.Lazy as BL (ByteString)
import qualified Data.HashMap.Strict as HashMap (empty, unions)
import Data.Function ((&))
import Data.Foldable (toList)

data Abc = Abc
  { name :: String
  , a :: Maybe String
  , b :: Maybe String
  } deriving (Generic, Show)

instance FromJSON Abc where
  parseJSON =
   withObject "Abc" $ \v -> do
    extra <- getExtra v
    Abc <$> v .: "name" <*> extra .:? "this_string_A" <*> extra .:? "this_string_B"

getExtra :: Object -> Parser Object
getExtra v = do
  mextra <- v .:? "extra"
  case mextra of
    Just vv -> vv & withArray "Abc.extra" (\arr -> do
      let vallst = toList arr
      objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
      return $ HashMap.unions objlst)
    Nothing -> return HashMap.empty

example :: BL.ByteString
example = "{\"name\": \"xyz1\", \"extra\": [{\"this_string_A\": \"Hello\"}, {\"this_string_B\": \"World\"}]}"

main = print (eitherDecode example :: Either String Abc)