Haskell:将可能是多种类型的对象解析为一种类型 [英] Haskell: Parsing an object that could be multiple types into one single type

查看:93
本文介绍了Haskell:将可能是多种类型的对象解析为一种类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是aeson的haskell初学者,通过解析一些数据文件来了解有关这两者的更多信息.

I'm a haskell beginner going through aeson, learning more about both by parsing some data files.

通常,当有数据文件时,可能是.jsonlua表,.csv格式或其他格式,而您要解析它们,总是有出错的可能性.

Usually when there's a data file, may it be .json, a lua table, .csv format or others, and you want to parse them, there's always a chance of error.

例如,像这样的简单.json文件

For example, a simple .json file like this

"root": {
     "m1": {
      "key1": "value1",
      "key2": 2
       },
     "m2": {
       "key1": 1
       },
}

具有两个奇数:"m1"有两个子项,一个在String中有一个值,而另一个在Int中有一个值. "m2"只有一个子键,它与上面的子键具有相同的键,但是值具有不同的类型,即. Int.

Has two oddities: "m1" has two subkeys, one has a value in String and one in Int. "m2" has only one subkey, and it has same key as the one above it but the value has a different type ie. Int.

如果是这样

"root": {
     "m1": {
      "key1": "value1",
      "key2": 2
       },
     "m2": {
      "key1": "value1",
      "key2": 2 
       },
}

使用Aeson解析它的一种简单方法就是使用这些数据类型

A simple way of parsing it with Aeson would be with these datatypes

data Root = Root { Map String Key
                 } deriving (Show, Generic)

data Key = Key { key1 :: String
               , key2 :: Int
               } deriving (Show, Generic)


如果缺少钥匙


If a key was missing

"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)


但是,如果像第一个示例那样,键不仅不具有值,而且具有完全不同的键,该怎么办?


But what if it were like the first example where not only can the keys not have a value but also have completely different ones.

如果您只关心数字或字符串怎么办?有没有一种方法可以解析它们而不会超出类型定义?

What if in them you only cared about the numbers or the strings? Would there be a way of parsing them without going out of the type definitions?

通过快速搜索,我发现Alternative类仅用于解决此类问题,并且*><><|>之类的运算符可能有用,但我不确定如何使用.

Going through some quick searches I found out the Alternative class is just meant for this kind of problems and operator like *>, <>, <|> can prove useful, but I'm not sure how.

我知道我需要定义一个可以封装所有三种可能性的类型,如果我只想要文本或数字,例如

I know I need to define a type that can encapsulate all three chances if I just wanted the text or numbers, like

Data NeededVal = NoValue | TextValue | Needed Int

Data NeededVal = NoValue | NumericValue | Needed String

但我不确定如何使它们成为Applicative&另一种选择,这样想法就可以解决.

but I'm not sure how I'd go about making them an instance of Applicative & Alternative so that the idea would work out.

这是我先前的问题的简短后续报告

推荐答案

好吧,我尝试使用JSON,如下所示:

Well, I try to play with the JSON as below:

"root": {
     "m1": {
      "key1": "value1",
      "key2": 2
       },
     "m2": {
       "key1": 1
       },
}

并使用

and parse it to the follow data types using 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,我将替代项<|>用作

To handle NoValue, I use Alternative <|> as

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)

要测试Stringnumeric类型,我将Value构造函数用作:

To test String and numeric type, I use Value constructor as:

instance FromJSON NeededVal where
    parseJSON (String txt) = return $ Needed $ unpack txt
    parseJSON (Number _)   = return $ NumericValue
    parseJSON _            = return NoValue

跳过m1m2对象,并立即以以下方式读取keys值:

To skip m1 and m2 objects and read the keys value immediately as:

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})]
))

旁注:

但我不确定如何将它们设置为 适用性另一种选择,这样想法就可以解决.

but I'm not sure how I'd go about making them an instance of Applicative & Alternative so that the idea would work out.

,无需将它们作为Applicative and Alternative的实例,<|>运算符应用于Parser(在Data.Aeson.Types中定义),而不是用户定义的数据类型. Parser已经是Alternative的实例.

No, No need to make them as an instance of Applicative and Alternative, the <|> operator apply on Parser (defined in Data.Aeson.Types) not the user defined data type. Parser has already be an instance of Alternative.

这篇关于Haskell:将可能是多种类型的对象解析为一种类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆