如何在 Haskell 中解析 JSON,其中字段名称可以是多个值之一,但应转换为单个 Haskell 类型?

Dar*_*997 2 parsing json haskell aeson

假设我有以下 JSON 值。

{
  "fieldName1": 5,
  "value1": "Hello"
}
Run Code Online (Sandbox Code Playgroud)

{
  "fieldName2": 7,
  "value1": "Welcome"
}
Run Code Online (Sandbox Code Playgroud)

我在 Haskell 中有以下类型。

data Greeting 
  = Greeting
      {
        count :: Int,
        name :: Text
      }
  deriving (Generic, Show, Eq)
Run Code Online (Sandbox Code Playgroud)

如何将此 JSON 解析为 Haskell,其中fieldName1fieldName2值应解析为count值?

我尝试通过执行如下所示的操作来解决此问题。

instance FromJSON Greeting where
  parseJSON = withObject "Greeting" $ \obj -> do
    count1 <- obj .:? "fieldName1"
    count2 <- obj .:? "fieldName2"
    name <- obj .: "value1"
    count <-
      case (count1, count2) of
        (Just count, Nothing) -> return count
        (Nothing, Just count) -> return count
        _ -> fail $ Text.unpack "Field missing"
    return Greeting {count = count, name = name}
Run Code Online (Sandbox Code Playgroud)

它可以工作,但非常麻烦,如果有两个以上的替代值,它就会变得更加复杂。有什么办法可以更简单地解决这个问题吗?

K. *_*uhr 5

Parser运行的 monad本身parseJSON就是一个,因此您可以在解析器定义中Alternative使用交替运算符:(<|>)

instance FromJSON Greeting where
  parseJSON = withObject "Greeting" $ \o -> do
    Greeting <$> (o .: "fieldName1" <|> o .: "fieldName2"
                  <|> fail "no count field")
             <*> o .: "value1"
Run Code Online (Sandbox Code Playgroud)

如果存在多个“count”字段,则将采用第一个解析的字段。

如果您想以更编程的方式处理字段名称(例如,如果您想接受名称以前缀开头的单个字段,"field"同时拒绝具有多个匹配字段的情况),请注意,o可以KeyMap使用Data.Aeson.KeyMapData.Aeson.Key模块:

import Data.Aeson
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import Data.List (isPrefixOf)

instance FromJSON Greeting where
  parseJSON = withObject "Greeting" $ \o -> do
    -- get all values for "field*" keys
    let fields = [v | (k, v) <- KeyMap.toList o
                    , "field" `isPrefixOf` Key.toString k]
    case fields of
      [v] -> Greeting <$> parseJSON v <*> o .: "value1"
      []  -> fail "no \"field*\" fields"
      _   -> fail "multiple \"field*\" fields"
Run Code Online (Sandbox Code Playgroud)