扩展Aeson例子,如何使用数组值?

Expanding Aeson example, how to use array of values?

我想多了解一点 Haskell,并且成功地遵循了 Aeson 的例子。我现在正在努力适应它,但我缺乏,而且我确信这是一个相当基本的理解。如果重要的话,我正在使用 IHaskell-aeson 包在 Jupyter Lab 设置中执行此操作。

初始示例假定输入 JSON 文件由单个简单对象组成。输出解析对象并提供一个小 greet 消息。

{
  "name": "Johann Carl Freidrich Guass",
  "nationality": "German",
  "born": "30 April 1777",
  "died": "23 February 1855"
}

输出:Johann Carl Freidrich Guass was born 30 April 1777

我想扩展它以使用它 JSON:

{
  "mathematicians": [
    { "name".... },
    { "name".... },
    { "name".... },
    { "name".... }
  ]
}

这是我现在的代码,我在第一个版本中留下了评论(:extension 用于 IHaskell/Jupyter):

:extension OverloadedStrings

import qualified Data.ByteString.Lazy as B
import Control.Monad (mzero)
import Data.Foldable (toList)
import Data.Aeson.Types (Parser)

data Mathematicians = Mathematicians
                    { mathematicians :: [Mathematician]
                    } deriving (Show)
data Mathematician = Mathematician 
                     { name :: String
                     , nationality :: String
                     , born :: String
                     , died :: Maybe String
                     } deriving (Show)
                     
instance A.FromJSON Mathematician where
    parseJSON (A.Object v) = Mathematician
                         <$> (v A..: "name")
                         <*> (v A..: "nationality")
                         <*> (v A..: "born")
                         <*> (v A..:? "died")
    parseJSON _ = mzero
                     
instance A.FromJSON Mathematicians where
    parseJSON (A.Object v) = do
        ms <- v A..: "mathematicians"
        fmap Mathematicians $ A.parseJSON ms
    parseJSON _ = mzero
                         
input <- B.readFile "mathematicians.json"

greet m = (show.name) m ++ 
          " was born " ++ 
          (show.born) m

-- let mm = A.decode input :: Maybe Mathematician
-- case mm of
--     Nothing -> print "error parsing JSON"
--     Just m -> (putStrLn.greet) m

let mms = A.decode input :: Maybe Mathematicians
case mms of
    Nothing -> print "error parsing JSON"
    Just ms -> print ms

打印 ms 给出了以下信息,这使我相信解析可能正在运行:

Mathematicians {mathematicians = [Mathematician {name = "Johann Carl Friedrich Gauss", nationality = "German", born = "30 April 1777", died = Just "23 February 1855"},Mathematician {name = "Leonhard Euler", nationality = "German", born = "15 April 1707", died = Just "18 September 1783"},Mathematician {name = "William Rowan Hamilton", nationality = "Irish", born = "3 August 1805", died = Just "2 September 1865"},Mathematician {name = "Terence Chi-Shen Tao", nationality = "Australia", born = "17 July 1975", died = Nothing}]}

此外,故意在 Mathematician 解析器中插入拼写错误会导致出现 error parsing JSON 消息,所以祈祷它看起来确实能够解析。

但是,我不明白如何访问单个 Mathematician 元素,然后按顺序对它们进行 运行 greet

我试过这样打印:print (fmap greet ms) 和 get

<interactive>:3:34: error:
    • Couldn't match expected type ‘f0 Mathematician’ with actual type ‘Mathematicians’
    • In the second argument of ‘fmap’, namely ‘ms’
      In the first argument of ‘print’, namely ‘(fmap greet ms)’
      In the expression: print (fmap greet ms)

改为使用print (map greet ms)

<interactive>:3:33: error:
    • Couldn't match expected type ‘[Mathematician]’ with actual type ‘Mathematicians’
    • In the second argument of ‘map’, namely ‘ms’
      In the first argument of ‘print’, namely ‘(map greet ms)’
      In the expression: print (map greet ms)

两者都不是,很抱歉,我完全理解。

mms 是一个 Maybe Mathematicians,这意味着它是一个可能不存在的 Mathematicians 值(有点像其他语言中的 null)。所以你需要在列表中的数学家运行宁greet之前“进入”Maybe

fmap :: (a -> b) -> Maybe a -> Maybe b 可以为您做到这一点。我们想给 fmap 一个函数,将 Mathematicians 变成一个字符串列表(所以 aMathematiciansb[String]) . fmap 将检查 Maybe、运行 函数是否有值,如果没有值则什么都不做。

greetMathematicians :: Maybe Mathematicians -> Maybe [String]
greetMathematicians mms = fmap (\ms -> [greet m | m <- mathematicians ms]) mms

您可以使用 mapM_ :: (Monad m, Foldable f) => (a -> m b) -> f a -> m () 到 运行 对 Foldable:

的所有元素的单子操作
greet :: Mathematician -> String
greet Mathematician { name=n, born=b } = n ++ " was born " ++ show b

main = do
    input <- B.readFile "mathematicians.json"
    case A.decode input :: Maybe Mathematicians of
    Nothing -> print "error parsing JSON"
    Just (Mathematicians ms) -> <b>mapM_ (putStrLn . greet) ms</b>