Haskell, Aeson - 有没有更好的解析历史数据的方法?
Haskell, Aeson - Is there a better way of parsing historical data?
'historical data' 我的意思是日期作为键,当天的值作为值。
例如,政府机构或大学的研究部门经常以这种格式编制有关地震、降雨、市场变动等的日期
{
"Meta Data": {
"1: Country": "SomeCountry",
"2: Region": "SomeRegion",
"3: Latest Recording": "2018-11-16"
},
"EarthQuakes": {
"2018-11-16": {
"Richter": "5.2508"
},
"2018-11-09": {
"Richter": "4.8684"
},
"2018-11-02": {
"Richter": "1.8399"
},
...
...
...
"1918-11-02": {
"Richter": "1.8399"
}
}
通常它会有一个 "Meta Data" 部分,而另一个包含 values/data.
作为初学者,我知道两种解析此类文档的方法。
要么使用 Aeson 文档中显示的一般解析,在其中定义数据类型,如下所示
Data MetaData = MetaData { country :: String, region :: String, latestRec :: String } deriving (Show, Eq, Generic)
使其成为FromJSON
的实例
instance FromJSON MetaData where
parseJSON = withObject "MetaData" $
\v -> do
metaData <- v .: pack "Meta Data"
country <- metaData .: pack "1: Country"
region <- metaData .: pack "2: Region"
latestRec <- metaData .: pack "3: Latest Recording"
return MetaData{..}
当然启用了 RecordWildCard
和 DeriveGenerics
扩展。
我看到的这种方法的问题是它不能很容易地在 "EarthQuakes" 部分实施。
我必须定义每个日期
earthQuakes <- v .: "EarthQuakes"
date1 <- earthQuakes .: "2018-11-16"
date2 <- earthQuakes .: "2018-11-06"
date3 <- earthQuakes .: "2018-11-02"
...
...
dateInfinity <- earthQuakes .: "1918-11-16"
更好的方法是通过将 link 解码为 Object
类型
来将所有数据解析为默认 JSON 值
thisFunction = do
linksContents <- simpleHttp "somelink"
let y = fromJust (decode linksContents :: Object)
z = aLotOfFunctionCompositions y
return z
其中 aLotOfFunctionCompositions
首先将 Object
转换为可能具有 [(k, v)]
对的 HashMap
。然后我会映射一个 unConstruct
函数来从默认构造函数中获取值,比如
unConstruct (DefaultType value) = case (DefaultType value) of
DefaultType x -> x
最后你会得到一个不错的列表!
这种方法的问题是 aLotOfFunctionComposition
。
这只是一个例子!但实际上它可能看起来像这样丑陋且不可读
let y = Prelude.map (\(a, b) -> (decode (encode a) :: Maybe String, decode (encode (snd (Prelude.head b))) :: Maybe String)) x
z = Prelude.map (\(a, b) -> (fromJust a, fromJust b)) y
a = Prelude.map (\(a, b) -> (a, read b :: Double)) z
b = Prelude.map (\(a, b) -> (Prelude.filter (/= '-') a, b)) a
c = Prelude.map (\(a, b) -> (read a :: Int, b)) b
这是我编写的工作代码的片段。
所以我的问题是:是否有一种 better/cleaner 方法来解码这些类型的 JSON 文件,其中你有很多 "dates" 键,你需要将它们解析成可行的数据类型?
在您的数据类型中输入 Map
。 Aeson 翻译 Map k v
s to/from 个对象,其中 v
s 是 en-/de-coded 通过他们自己的 To
-/From
-JSON
To
-/From
-JSONKey
s 的实例和 k
s。事实证明 Day
(来自 time
包)具有非常合适的 To
-/From
-JSONKey
个实例。
data EarthquakeData = EarthquakeData {
metaData :: MetaData,
earthquakes :: Map Day Earthquake
} deriving (Eq, Show, Generic)
instance FromJSON EarthquakeData where
parseJSON = withObject "EarthquakeData $ \v ->
EarthquakeData <$> v .: "Meta Data"
-- Map k v has a FromJSON instance that just does the right thing
-- so just get the payloads with (.:)
-- all this code is actually just because your field names are really !#$@~??
-- not an Aeson expert, maybe there's a better way
<*> v .: "EarthQuakes"
instance ToJSON EarthquakeData where
toJSON EarthquakeData{..} = object [ "Meta Data" .= metaData
, "EarthQuakes" .= earthquakes
]
data MetaData = MetaData { country :: String, region :: String, latestRec :: Day } deriving (Eq, Show)
instance FromJSON MetaData where
parseJSON = withObject "MetaData" $ \v ->
-- if you haven't noticed, applicative style is much neater than do
-- using OverloadedStrings avoids all the pack-ing static
MetaData <$> v .: "1: Country"
<*> v .: "2: Region"
<*> v .: "3: Latest Recording"
instance ToJSON MetaData where
toJSON MetaData{..} = object [ "1: Country" .= country
, "2: Region" .= region
, "3: Latest Recording" .= latestRec
]
toEncoding MetaData{..} = pairs $ "1: Country" .= country
<> "2: Region" .= region
<> "3: Latest Recording" .= latestRec
data Earthquake = Earthquake { richter :: Double } deriving (Eq, Show)
-- Earthquake is a bit funky because your JSON apparently has
-- numbers inside strings?
-- only here do you actually need monadic operations
instance FromJSON Earthquake where
parseJSON = withObject "Earthquake" $ \v ->
do string <- v .: "Richter"
stringNum <- parseJSON string
case readMaybe stringNum of
Just num -> return $ Earthquake num
Nothing -> typeMismatch "Double inside a String" string
instance ToJSON Earthquake where
toJSON = object . return . ("Richter" .=) . show . richter
toEncoding = pairs . ("Richter" .=) . show . richter
我已经根据您的示例 JSON 对此进行了测试,它似乎成功往返 encode
和 decode
。
'historical data' 我的意思是日期作为键,当天的值作为值。
例如,政府机构或大学的研究部门经常以这种格式编制有关地震、降雨、市场变动等的日期
{
"Meta Data": {
"1: Country": "SomeCountry",
"2: Region": "SomeRegion",
"3: Latest Recording": "2018-11-16"
},
"EarthQuakes": {
"2018-11-16": {
"Richter": "5.2508"
},
"2018-11-09": {
"Richter": "4.8684"
},
"2018-11-02": {
"Richter": "1.8399"
},
...
...
...
"1918-11-02": {
"Richter": "1.8399"
}
}
通常它会有一个 "Meta Data" 部分,而另一个包含 values/data.
作为初学者,我知道两种解析此类文档的方法。
要么使用 Aeson 文档中显示的一般解析,在其中定义数据类型,如下所示
Data MetaData = MetaData { country :: String, region :: String, latestRec :: String } deriving (Show, Eq, Generic)
使其成为FromJSON
instance FromJSON MetaData where
parseJSON = withObject "MetaData" $
\v -> do
metaData <- v .: pack "Meta Data"
country <- metaData .: pack "1: Country"
region <- metaData .: pack "2: Region"
latestRec <- metaData .: pack "3: Latest Recording"
return MetaData{..}
当然启用了 RecordWildCard
和 DeriveGenerics
扩展。
我看到的这种方法的问题是它不能很容易地在 "EarthQuakes" 部分实施。
我必须定义每个日期
earthQuakes <- v .: "EarthQuakes"
date1 <- earthQuakes .: "2018-11-16"
date2 <- earthQuakes .: "2018-11-06"
date3 <- earthQuakes .: "2018-11-02"
...
...
dateInfinity <- earthQuakes .: "1918-11-16"
更好的方法是通过将 link 解码为 Object
类型
thisFunction = do
linksContents <- simpleHttp "somelink"
let y = fromJust (decode linksContents :: Object)
z = aLotOfFunctionCompositions y
return z
其中 aLotOfFunctionCompositions
首先将 Object
转换为可能具有 [(k, v)]
对的 HashMap
。然后我会映射一个 unConstruct
函数来从默认构造函数中获取值,比如
unConstruct (DefaultType value) = case (DefaultType value) of
DefaultType x -> x
最后你会得到一个不错的列表!
这种方法的问题是 aLotOfFunctionComposition
。
这只是一个例子!但实际上它可能看起来像这样丑陋且不可读
let y = Prelude.map (\(a, b) -> (decode (encode a) :: Maybe String, decode (encode (snd (Prelude.head b))) :: Maybe String)) x
z = Prelude.map (\(a, b) -> (fromJust a, fromJust b)) y
a = Prelude.map (\(a, b) -> (a, read b :: Double)) z
b = Prelude.map (\(a, b) -> (Prelude.filter (/= '-') a, b)) a
c = Prelude.map (\(a, b) -> (read a :: Int, b)) b
这是我编写的工作代码的片段。
所以我的问题是:是否有一种 better/cleaner 方法来解码这些类型的 JSON 文件,其中你有很多 "dates" 键,你需要将它们解析成可行的数据类型?
在您的数据类型中输入 Map
。 Aeson 翻译 Map k v
s to/from 个对象,其中 v
s 是 en-/de-coded 通过他们自己的 To
-/From
-JSON
To
-/From
-JSONKey
s 的实例和 k
s。事实证明 Day
(来自 time
包)具有非常合适的 To
-/From
-JSONKey
个实例。
data EarthquakeData = EarthquakeData {
metaData :: MetaData,
earthquakes :: Map Day Earthquake
} deriving (Eq, Show, Generic)
instance FromJSON EarthquakeData where
parseJSON = withObject "EarthquakeData $ \v ->
EarthquakeData <$> v .: "Meta Data"
-- Map k v has a FromJSON instance that just does the right thing
-- so just get the payloads with (.:)
-- all this code is actually just because your field names are really !#$@~??
-- not an Aeson expert, maybe there's a better way
<*> v .: "EarthQuakes"
instance ToJSON EarthquakeData where
toJSON EarthquakeData{..} = object [ "Meta Data" .= metaData
, "EarthQuakes" .= earthquakes
]
data MetaData = MetaData { country :: String, region :: String, latestRec :: Day } deriving (Eq, Show)
instance FromJSON MetaData where
parseJSON = withObject "MetaData" $ \v ->
-- if you haven't noticed, applicative style is much neater than do
-- using OverloadedStrings avoids all the pack-ing static
MetaData <$> v .: "1: Country"
<*> v .: "2: Region"
<*> v .: "3: Latest Recording"
instance ToJSON MetaData where
toJSON MetaData{..} = object [ "1: Country" .= country
, "2: Region" .= region
, "3: Latest Recording" .= latestRec
]
toEncoding MetaData{..} = pairs $ "1: Country" .= country
<> "2: Region" .= region
<> "3: Latest Recording" .= latestRec
data Earthquake = Earthquake { richter :: Double } deriving (Eq, Show)
-- Earthquake is a bit funky because your JSON apparently has
-- numbers inside strings?
-- only here do you actually need monadic operations
instance FromJSON Earthquake where
parseJSON = withObject "Earthquake" $ \v ->
do string <- v .: "Richter"
stringNum <- parseJSON string
case readMaybe stringNum of
Just num -> return $ Earthquake num
Nothing -> typeMismatch "Double inside a String" string
instance ToJSON Earthquake where
toJSON = object . return . ("Richter" .=) . show . richter
toEncoding = pairs . ("Richter" .=) . show . richter
我已经根据您的示例 JSON 对此进行了测试,它似乎成功往返 encode
和 decode
。