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