如何在Haskell中用可选和变量类型的字段解析json?

如何在Haskell中用可选和变量类型的字段解析json?,json,haskell,functional-programming,Json,Haskell,Functional Programming,如何解析此文件中的输入json 对于次要和标志属性?它们是可选的,并且包含变量类型 一个最简单的例子是: [ {}, { "secondary": false }, { "secondary": { "chance": 10, "boosts": { "spd": -1 } } }, {

如何解析此文件中的输入json

对于次要和标志属性?它们是可选的,并且包含变量类型

一个最简单的例子是:

[
  {},
  {
    "secondary": false
  },
  {

    "secondary": {
      "chance": 10,
      "boosts": {
        "spd": -1
      }
    }
  },
  {
    "secondary": {
      "chance": 30,
      "volatileStatus": "flinch"
    }
  },
  {
    "secondary": {
      "chance": 30
    }
  },
  {
    "secondary": {
      "chance": 10,
      "self": {
        "boosts": {
          "atk": 1,
          "def": 1,
          "spa": 1,
          "spd": 1,
          "spe": 1
        }
      }
    }
  },
  {
    "secondary": {
      "chance": 10,
      "status": "brn"
    }
  },
  {
    "secondary": {
      "chance": 50,
      "self": {
        "boosts": {
          "def": 2
        }
      }
    }
  },
  {
    "secondary": {
      "chance": 100,
      "self": {}
    }
  },
  {
    "secondary": {
      "chance": 50,
      "boosts": {
        "accuracy": -1
      }
    }
  }
]
为了方便起见,您可以选择将此代码段附加到js文件的末尾,并使用node move.js运行它。两个有效的json文件将保存到磁盘中。一个是json对象列表,另一个是以字符串为键的对象


var fs = require('fs');
fs.writeFile("moves_object.json", JSON.stringify(BattleMovedex), function(err) {}); // 1. save a json object with string key

var jsonList = []
for (var key of Object.keys(BattleMovedex)) {
    jsonList.push(BattleMovedex[key]);
}
fs.writeFile("moves.json", JSON.stringify(jsonList), function(err) { // 2. save as a list of json object
    if (err) {
        console.log(err);
    }
});

供参考: 如果您熟悉C++,您可能会更容易理解这个帖子中的相同问题:

注意:在下面的代码示例中,我使用了一个moves.json文件,其内容是上面的最小示例。除了可以解析任何有效JSON的getMoves之外,其他代码示例在从链接的moves.js文件派生的moves.JSON文件上不起作用,因为格式不同,例如,它是一个对象,而不是数组

使用Aeson解析任意JSON的最简单方法是将其解析为一个值:

哪些产出:

> test1
Just (Object (fromList [("secondary",Object (fromList [("chance",Number 100.0),
("self",Object (fromList []))]))]))
这是对象的值表示形式:

{
  "secondary": {
    "chance": 100,
    "self": {}
  }
}
如果希望得到的解析对象具有更多的结构,那么首先需要找出一个Haskell表示法,该表示法可以处理所有可能要解析的对象。例如,合理的表述可能是:

这假设所有移动都有一个辅助字段,该字段为false或object。它还假设可以使用很多boost键,因此在Boosts hashmap中将它们表示为任意文本字符串更方便。此外,这还处理了将boost直接放在secondary下或嵌套在self中的问题,因为您的示例包含了这两种形式的示例,尽管这可能是一个错误

对于这些数据类型,可以使用Move、Self和Secondary的默认实例:

instance FromJSON Move
instance FromJSON Self
instance FromJSON Secondary
然后使用Secondary的newtype包装器来处理false与使用自定义实例的对象:

instance FromJSON Secondary' where
  parseJSON (Bool False) = pure $ Secondary' Nothing
  parseJSON o = Secondary' . Just <$> parseJSON o
现在,使用以下驱动程序:

test2 :: IO (Either String Moves)
test2 = eitherDecode <$> B.readFile "moves.json"
通过使用上面的eitherDecode,如果解析失败,我们可以得到一条错误消息。例如,如果在从moves.js派生的moves.json上运行此命令,则会得到:

> test2
Left "Error in $: parsing [] failed, expected Array, but encountered Object"
当解析器注意到它正试图解析[Move]数组,但却在查找由Pokemon Move name设置键的对象时

以下是显示这两种解析类型的完整代码:

{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}

import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import GHC.Generics
import qualified Data.Text as Text
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Vector as Vector
import qualified Data.ByteString.Lazy as B

--
-- Parse into a dynamic Value representation

getMoves :: IO Value
getMoves = do
  mv <- decode <$> B.readFile "moves.json"
  case mv of
    Nothing -> error "invalid JSON"
    Just v -> return v

find100 :: Value -> Maybe Value
find100 inp = do
  arr <- inp ^? _Array
  Vector.find (\s -> s ^? key "secondary" . key "chance" . _Integer == Just 100) arr

test1 :: IO (Maybe Value)
test1 = find100 <$> getMoves

--
-- Parse into suitable static data structures

-- whole file is array of moves
type Moves = [Move]

data Move = Move
  { secondary :: Secondary'
  } deriving (Show, Generic)

newtype Secondary' = Secondary' (Maybe Secondary) -- Nothing if json is "false"
  deriving (Show, Generic)

data Secondary = Secondary
  { chance :: Maybe Int
  , boosts :: Maybe Boosts
  , volatileStatus :: Maybe String
  , self :: Maybe Self
  } deriving (Show, Generic)

data Self = Self
  { boosts :: Maybe Boosts
  } deriving (Show, Generic)

newtype Boosts = Boosts (HashMap.HashMap Text.Text Int)
  deriving (Show, Generic)

instance FromJSON Move
instance FromJSON Self
instance FromJSON Secondary

instance FromJSON Secondary' where
  parseJSON (Bool False) = pure $ Secondary' Nothing
  parseJSON o = Secondary' . Just <$> parseJSON o

instance FromJSON Boosts where
  parseJSON = withObject "Boosts" $ \o -> Boosts <$> mapM parseJSON o

test2 :: IO (Either String Moves)
test2 = eitherDecode <$> B.readFile "moves.json"

我对最小样本的尝试

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


module Main where

import Data.Text
import GHC.Generics
import Data.Aeson

main :: IO ()
main = do
  result <- eitherDecodeFileStrict "/tmp/helloworld/minimal.json"
  print (result :: Either String [Foo])

data Foo = Foo { secondary :: Either Bool Bar } deriving (Generic, Show)
data Bar = Chance
  { chance :: Int
  , volatileStatus :: Maybe Text
  , boosts :: Maybe Boosts
  , self :: Maybe Self
  , status :: Maybe Text
  } deriving (Generic, Show)

data Boosts = Boosts
  { atk :: Maybe Int
  , def :: Maybe Int
  , spa :: Maybe Int
  , spd :: Maybe Int
  , spe :: Maybe Int
  } deriving (Generic, Show)

data Self = Self
  { boosts :: Maybe Boosts
  } deriving (Generic, Show)

instance FromJSON Foo where
  parseJSON (Object v) = do
    sd <- v .: "secondary"  -- Parse Value
    case sd of
      Bool x -> return . Foo . Left $ x
      otherwise -> (Foo . Right) <$> parseJSON sd
instance FromJSON Bar
instance FromJSON Boosts
instance FromJSON Self

下面是对mover.json的另一次尝试


你的问题是什么?如果你的代码不起作用,我们可以建议你,但是,一般来说,在这个网站上不会回答家庭作业问题。@BobDalgleish这个问题就是标题。这是一个项目,我希望这是一个有答案的家庭作业问题。问题是我不确定这在haskell是否可行。boiler板显示了解析简单字段的方法,但不是同时解析可选字段和变量字段。js文件链接显示了完整的输入@哦,我明白了!教程链接的域名中有学校!Data.Aeson可能支持可选元素的类型,变量值的GADT类型。我找不到任何GADT解析Json的工作代码示例,是否有在线教程可用于Aeson或通过实现Json解析器单独使用它?解决方案中使用的HashMap.HashMap也是一种非常好的处理方法,尽管领域知识显示它的密钥数量有限,但我尝试将您的代码加载到ghci中。getMoves返回json,但test1和test2都返回*Main>test2 Nothing我如何修复它,或者如何将整个过程转换为一个主模块以便编译和运行二进制文件?他们没有返回任何内容,因为解析失败,可能是因为你的moves.json与我的不同。我应该明确指出,我的moves.json内容是您的69行最小示例,而不是从moves.js生成的版本,它的格式完全不同,它是一个对象而不是一个数组,因此test1和test2都将立即无法解析它。谢谢!为了进一步澄清,我更新了javascript代码片段,以使派生的moves.json与最小示例保持一致。现在test1仍然可以工作,但是test2什么也不返回。出于我不理解的原因,ghci没有显示任何关于解析错误在哪里的提示?有什么方法可以显示第一个解析错误在哪里吗?我已经尝试调整您的解决方案来解析整个json,但是现在解析的准确性是一个新问题@K.A.布尔
test2 :: IO (Either String Moves)
test2 = eitherDecode <$> B.readFile "moves.json"
> test2
Right [Move {secondary = Secondary' Nothing},Move {secondary =
Secondary' (Just (Secondary {chance = Just 10, boosts = Just (Boosts
(fromList [("spd",-1)])), volatileStatus = Nothing, self =
...
> test2
Left "Error in $: parsing [] failed, expected Array, but encountered Object"
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}

import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import GHC.Generics
import qualified Data.Text as Text
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Vector as Vector
import qualified Data.ByteString.Lazy as B

--
-- Parse into a dynamic Value representation

getMoves :: IO Value
getMoves = do
  mv <- decode <$> B.readFile "moves.json"
  case mv of
    Nothing -> error "invalid JSON"
    Just v -> return v

find100 :: Value -> Maybe Value
find100 inp = do
  arr <- inp ^? _Array
  Vector.find (\s -> s ^? key "secondary" . key "chance" . _Integer == Just 100) arr

test1 :: IO (Maybe Value)
test1 = find100 <$> getMoves

--
-- Parse into suitable static data structures

-- whole file is array of moves
type Moves = [Move]

data Move = Move
  { secondary :: Secondary'
  } deriving (Show, Generic)

newtype Secondary' = Secondary' (Maybe Secondary) -- Nothing if json is "false"
  deriving (Show, Generic)

data Secondary = Secondary
  { chance :: Maybe Int
  , boosts :: Maybe Boosts
  , volatileStatus :: Maybe String
  , self :: Maybe Self
  } deriving (Show, Generic)

data Self = Self
  { boosts :: Maybe Boosts
  } deriving (Show, Generic)

newtype Boosts = Boosts (HashMap.HashMap Text.Text Int)
  deriving (Show, Generic)

instance FromJSON Move
instance FromJSON Self
instance FromJSON Secondary

instance FromJSON Secondary' where
  parseJSON (Bool False) = pure $ Secondary' Nothing
  parseJSON o = Secondary' . Just <$> parseJSON o

instance FromJSON Boosts where
  parseJSON = withObject "Boosts" $ \o -> Boosts <$> mapM parseJSON o

test2 :: IO (Either String Moves)
test2 = eitherDecode <$> B.readFile "moves.json"
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}


module Main where

import Data.Text
import GHC.Generics
import Data.Aeson

main :: IO ()
main = do
  result <- eitherDecodeFileStrict "/tmp/helloworld/minimal.json"
  print (result :: Either String [Foo])

data Foo = Foo { secondary :: Either Bool Bar } deriving (Generic, Show)
data Bar = Chance
  { chance :: Int
  , volatileStatus :: Maybe Text
  , boosts :: Maybe Boosts
  , self :: Maybe Self
  , status :: Maybe Text
  } deriving (Generic, Show)

data Boosts = Boosts
  { atk :: Maybe Int
  , def :: Maybe Int
  , spa :: Maybe Int
  , spd :: Maybe Int
  , spe :: Maybe Int
  } deriving (Generic, Show)

data Self = Self
  { boosts :: Maybe Boosts
  } deriving (Generic, Show)

instance FromJSON Foo where
  parseJSON (Object v) = do
    sd <- v .: "secondary"  -- Parse Value
    case sd of
      Bool x -> return . Foo . Left $ x
      otherwise -> (Foo . Right) <$> parseJSON sd
instance FromJSON Bar
instance FromJSON Boosts
instance FromJSON Self
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}


module Main where

import Control.Applicative
import Data.Maybe
import Data.Text (Text)
import GHC.Generics
import Data.Aeson

main :: IO ()
main = do
  result <- eitherDecodeFileStrict "/tmp/helloworld/movers.json" 
  case ( result :: Either String [Move]) of
    Left error -> print error
    Right ms -> print (length ms)

data Move = Move
  { num :: Int
  , accuracy :: Either Int Bool
  , secondary :: Maybe (Either Bool Secondary)
  } deriving (Generic, Show)

data Secondary = Secondary
  { chance :: Maybe Int
  , volatileStatus :: Maybe Text
  , boosts :: Maybe Boosts
  , self :: Maybe Self
  , status :: Maybe Text
  } deriving (Generic, Show)

data Boosts = Boosts
  { atk :: Maybe Int
  , def :: Maybe Int
  , spa :: Maybe Int
  , spd :: Maybe Int
  , spe :: Maybe Int
  } deriving (Generic, Show)

data Self = Self
  { boosts :: Maybe Boosts
  } deriving (Generic, Show)

instance FromJSON Move where
  parseJSON (Object v) = Move
    <$> v .: "num"
    <*> (   (Left  <$> v .: "accuracy")
        <|> (Right <$> v .: "accuracy")
        )
    <*> (   fmap (fmap Left)  (v .:? "secondary")
        <|> fmap (fmap Right) (v .:? "secondary")
        )

instance FromJSON Secondary
instance FromJSON Boosts
instance FromJSON Self