如何解析该文件中的输入json? https://github.com/smogon/pokemon-showdown/blob/master/data/moves.js
对于次要属性和标志属性?它们是可选的,并且包含变量类型。
一个最小的例子就是这个:
[
{},
{
"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
}
}
}
]
预期输出可以但不限于:1.解析的对象具有更多的结构,以便我们可以通过键访问(如果存在)2.它具有动态结构,因此我们可以在运行时检查密钥是否存在。例如,“ boosts”仅具有有限数量(<10)个可能的键。
为了方便起见,您可以选择将此代码段附加到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);
}
});
以锅炉板为起点:
{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
import Data.Aeson
import Data.Text
import Control.Applicative
import Control.Monad
import qualified Data.ByteString.Lazy as B
import Network.HTTP.Conduit (simpleHttp)
import GHC.Generics
-- | Type of each JSON entry in record syntax.
data Person =
Person { firstName :: !Text
, lastName :: !Text
, age :: Int
, likesPizza :: Bool
} deriving (Show,Generic)
-- Instances to convert our type to/from JSON.
instance FromJSON Person
instance ToJSON Person
-- | Location of the local copy, in case you have it,
-- of the JSON file.
jsonFile :: FilePath
jsonFile = "pizza.json"
-- | URL that points to the remote JSON file, in case
-- you have it.
jsonURL :: String
jsonURL = "http://daniel-diaz.github.io/misc/pizza.json"
-- Move the right brace (}) from one comment to another
-- to switch from local to remote.
{--
-- Read the local copy of the JSON file.
getJSON :: IO B.ByteString
getJSON = B.readFile jsonFile
--}
{--}
-- Read the remote copy of the JSON file.
getJSON :: IO B.ByteString
getJSON = simpleHttp jsonURL
--}
main :: IO ()
main = do
-- Get JSON data and decode it
d <- (eitherDecode <$> getJSON) :: IO (Either String [Person])
-- If d is Left, the JSON was malformed.
-- In that case, we report the error.
-- Otherwise, we perform the operation of
-- our choice. In this case, just print it.
case d of
Left err -> putStrLn err
Right ps -> print ps
更具挑战性的功能部分:
onSetStatus
的effect
,以及genesissupernova
的情况,其中self
可以包含函数onHit
]。为简单起见,该问题不是必需的,但是非常有趣的是,看看像Haskell这样的函数式编程语言如何能够像Javascript这样支持它。FYI:如果您熟悉c ++,可能会发现在这篇文章中更容易理解相同的问题:How to parse json file with type composition of std::optional and std::variant
NOTE:在下面的代码示例中,我使用了一个"moves.json"
文件,其内容是上面的最小示例。除了可以解析任何有效JSON的getMoves
之外,其他代码示例也不适用于从链接的"moves.json"
文件派生的"moves.js"
文件,因为格式不同(例如,它是一个对象,而不是数组) ,因为一件事)。
使用Aeson解析任意JSON的最简单方法是将其解析为Value
:
import Data.Aeson
import Data.Maybe
import qualified Data.ByteString.Lazy as B
getMoves :: IO Value
getMoves = do
mv <- decode <$> B.readFile "moves.json"
case mv of
Nothing -> error "invalid JSON"
Just v -> return v
任何有效的JSON都可以通过这种方式进行解析,并且生成的Value
具有完全动态的结构,可以在运行时以编程方式对其进行检查。镜头库和Maybe
monad在这里可能会有所帮助。例如,要查找secondary.chance
为100的(第一个)对象,可以使用:
{-# LANGUAGE OverloadedStrings #-}
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import qualified Data.Vector as Vector
import qualified Data.ByteString.Lazy as B
find100 :: Value -> Maybe Value
find100 inp = do
arr <- inp ^? _Array
Vector.find (\s -> s ^? key "secondary" . key "chance" . _Integer == Just 100) arr
test1 = find100 <$> getMoves
输出:
> test1
Just (Object (fromList [("secondary",Object (fromList [("chance",Number 100.0),
("self",Object (fromList []))]))]))
这是对象的Value
表示形式:
{
"secondary": {
"chance": 100,
"self": {}
}
}
如果要使生成的已解析对象具有更多的结构,那么首先需要弄清楚一个Haskell表示形式,该表示形式将与您打算解析的所有可能的对象一起使用。对于您的示例,合理的表示方式[[might为:]]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)
这假定所有移动都具有一个secondary
字段,该字段可以是"false"
或对象。它还假定可以使用许多升压键,因此在Boosts
哈希图中将它们表示为任意文本字符串更为方便。另外,由于您的示例包含了两种形式的示例,因此这可以将"boosts"
直接置于"secondary"
之下或嵌套在"self"
中,尽管这可能是一个错误。
对于这些数据类型,可以使用Move
,Self
和Secondary
的默认实例:
instance FromJSON Move
instance FromJSON Self
instance FromJSON Secondary
然后使用自定义实例将Secondary'
周围的Secondary
新型包装器用于处理false
与对象:
instance FromJSON Secondary' where
parseJSON (Bool False) = pure $ Secondary' Nothing
parseJSON o = Secondary' . Just <$> parseJSON o
Boosts
还需要一个自定义实例,以将其解析为适当的哈希图:
instance FromJSON Boosts where
parseJSON = withObject "Boosts" $ \o -> Boosts <$> mapM parseJSON o
现在,使用以下驱动程序:
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 =
...
通过使用上面的eitherDecode
,如果解析失败,我们会收到一条错误消息。例如,如果您在从"moves.json"
派生的"moves.js"
上运行此命令,则会得到:
> test2
Left "Error in $: parsing [] failed, expected Array, but encountered Object"
[当分析器注意到它正在尝试分析[Move]
数组,但正在查找由Pokemon移动名称作为键的对象时。
这是显示两种解析类型的完整代码:
{-# 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"
mover.json
的另一种尝试一个不令人满意的答案