[英]Haskell: Parsing an object that could be multiple types into one single type
我是一個使用 aeson 的 Haskell 初學者,通過解析一些數據文件來了解更多關於兩者的信息。
通常當有數據文件時,可能是.json
、 lua
表、 .csv
格式或其他格式,你想解析它們,總是有出錯的機會。
例如,像這樣一個簡單的.json
文件
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": 1
},
}
有兩個奇怪的地方: "m1"
有兩個子鍵,一個在String
有一個值,另一個在Int
。 "m2"
只有一個子鍵,它和它上面的鍵有相同的鍵,但值有不同的類型,即。 Int
。
如果是這樣
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": "value1",
"key2": 2
},
}
使用 Aeson 解析它的一種簡單方法是使用這些數據類型
data Root = Root { Map String Key
} deriving (Show, Generic)
data Key = Key { key1 :: String
, key2 :: Int
} deriving (Show, Generic)
如果缺少鑰匙
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": "value1"
},
}
這本可以完成這項工作
data Root = Root { Map String Key
} deriving (Show, Generic)
data Key = Key { key1 :: String
, key2 :: Maybe Int
} deriving (Show, Generic)
但是,如果它就像第一個示例一樣,其中鍵不僅可以沒有值,而且還可以具有完全不同的值。
如果在它們中你只關心數字或字符串怎么辦? 有沒有一種方法可以在不脫離類型定義的情況下解析它們?
經過一些快速搜索,我發現 Alternative 類僅適用於此類問題,並且像*>
、 <>
、 <|>
這樣的運算符可以證明很有用,但我不確定如何。
我知道如果我只想要文本或數字,我需要定義一個可以封裝所有三個機會的類型,比如
Data NeededVal = NoValue | TextValue | Needed Int
或者
Data NeededVal = NoValue | NumericValue | Needed String
但我不確定如何讓它們成為 Applicative & Alternative 的實例,以便這個想法能夠實現。
這是我上一個問題的簡短后續
好吧,我嘗試使用 JSON,如下所示:
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": 1
},
}
並使用Data.Aeson將其解析為以下數據類型:
data Root = Root (Map String Key) deriving (Show)
data NeededVal = NoValue | NumericValue | Needed String deriving (Show)
data Key = Key { key1 :: NeededVal , key2 :: NeededVal } deriving (Show)
為了處理NoValue
,我使用 Alternative <|>
作為
instance FromJSON Key where
parseJSON = withObject "Key" $ \obj -> do
k1 <- obj .: (pack "key1") <|> pure NoValue
k2 <- obj .: (pack "key2") <|> pure NoValue
return(Key k1 k2)
為了測試String
和numeric
類型,我使用Value
構造函數作為:
instance FromJSON NeededVal where
parseJSON (String txt) = return $ Needed $ unpack txt
parseJSON (Number _) = return $ NumericValue
parseJSON _ = return NoValue
跳過m1
和m2
對象並立即讀取keys
,如下所示:
import Data.Map as Map (Map, fromList)
import Data.HashMap.Strict as HM (toList, lookup)
import Data.Aeson.Types (Parser)
parseJSON = withObject "Root"
$ \rootObj-> case HM.lookup (pack "root") rootObj of
Nothing -> fail "no Root"
Just val -> withObject "Key List" mkRoot val
where mkRoot obj =
let (ks, vs) = unzip $ HM.toList obj
ks' = map unpack ks
in do vs' <- mapM parseJSON vs::Parser [Key]
return $ Root $ Map.fromList $ zip ks' vs'
和最終結果:
Right (Root (fromList [
("m1",Key {key1 = Needed "value1", key2 = NumericValue}),
("m2",Key {key1 = NumericValue, key2 = NoValue})]
))
旁注:
但我不確定如何讓它們成為 Applicative & Alternative 的實例,以便這個想法能夠實現。
不,不需要將它們作為Applicative and Alternative
的實例, <|>
運算符應用於Parser
(在Data.Aeson.Types
定義)而不是用戶定義的數據類型。 Parser
已經是Alternative
一個實例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.