Haskell JSON 用 Aeson 解析
Haskell JSON parsing with Aeson
我有一个 JSON 数据源,如下所示:
{ "fields": [
{ "type": "datetime",
"name": "Observation Valid",
"description": "Observation Valid Time"},
{ "type": "datetime",
"name": "Observation Valid UTC",
"description": "Observation Valid Time UTC"},
{ "type": "number",
"name": "Air Temperature[F]",
"description": "Air Temperature at 2m AGL"},
{ "type": "number",
"name": "Wind Speed[kt]",
"description": "Wind Speed"},
{ "type": "number",
"name": "Wind Gust[kt]",
"description": "Wind Gust"},
{ "type": "number", "name":
"Wind Direction[deg]",
"description": "Wind Direction"}
],
"rows": [
["2018-04-22T00:10:00", "2018-04-22T05:10:00Z", 50.0, 9.0, null, 50.0],
["2018-04-22T00:15:00", "2018-04-22T05:15:00Z", 50.0, 9.0, null, 60.0],
["2018-04-22T00:20:00", "2018-04-22T05:20:00Z", 50.0, 8.0, null, 60.0],
["2018-04-22T00:30:00", "2018-04-22T05:30:00Z", 50.0, 9.0, null, 60.0]
]
}
( https://mesonet.agron.iastate.edu/json/obhistory.py?station=TVK&network=AWOS&date=2018-04-22 )
并尝试了几种数据描述,最后是这样的:
data Entry = -- Data entries
Entry { time :: Text -- Observation Valid Time
, timeUTC :: Text -- Observation Valid Time UTC
, airTemp :: Float -- Air Temperature[F] at 2m AGL
, wind :: Float -- Wind Speed [kt]
, gust :: Float -- Wind Gust [kt]
, direction :: Int -- Wind Direction[deg]
} deriving (Show,Generic)
data Field = -- Schema Definition
Field { ftype :: String --
, name :: String --
, description :: String --
} deriving (Show,Generic)
data Record =
Record { fields :: [Field] --
, rows :: [Entry] -- data
} deriving (Show,Generic)
-- Instances to convert our type to/from JSON.
instance FromJSON Entry
instance FromJSON Field
instance FromJSON Record
-- Get JSON data and decode it
dat <- (eitherDecode <$> getJSON) :: IO (Either String Record)
这给出了这个错误:
$.fields[0] 中的错误:键 "ftype" 不存在
(第一个)错误来自字段定义(我不使用)。在 JSON 中,条目是混合类型的数组,但在 Haskell 中,它只是一个数据结构,而不是数组——不确定如何协调这些。
毫无疑问是初学者的错误——但我还没有找到任何似乎具有这种结构的例子。我需要为此编写自定义解析器吗?
Field 有一个 ftype
字段,因此 AESON 试图在 JSON 中找到 ftype 但找不到(因为它包含 ftype)。我知道您不能在 Haskell 中命名字段 type
,因此您需要找到一种方法让 AESON 使用不同的名称。您需要使用模板 Haskell 并相应地设置 fieldLabelModifier。或者,手动编写坚持可能更简单。
三件事阻止了它按预期工作:
- JSON 数据包含一个名为 "type" 的字段。
Field
记录类型的自定义 FromJson
实例可以处理此问题。
Entry
类型中的数据未命名,因此最好将其表示为不带字段名称的 data
记录或 tuple
。
- 代表阵风的
Float
有时是null
所以应该是Maybe Float
下面的代码包含所有这些修改并解析您的示例 JSON 数据:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.ByteString.Lazy as BSL
import Data.Text (Text)
import Data.Aeson
import GHC.Generics
-- Either this tuple definition of Entry or the data definition without
-- names (commented out) will work.
type Entry = -- Data entries
( Text -- Observation Valid Time
, Text -- Observation Valid Time UTC
, Float -- Air Temperature[F] at 2m AGL
, Float -- Wind Speed [kt]
, Maybe Float -- Wind Gust [kt]
, Int -- Wind Direction[deg]
)
-- data Entry = -- Data entries
-- Entry Text -- Observation Valid Time
-- Text -- Observation Valid Time UTC
-- Float -- Air Temperature[F] at 2m AGL
-- Float -- Wind Speed [kt]
-- (Maybe Float) -- Wind Gust [kt]
-- Int -- Wind Direction[deg]
-- deriving (Show,Generic)
-- instance FromJSON Entry
data Field = -- Schema Definition
Field { ftype :: String --
, name :: String --
, description :: String --
} deriving (Show,Generic)
instance FromJSON Field where
parseJSON = withObject "Field" $ \v -> Field
<$> v .: "type"
<*> v .: "name"
<*> v .: "description"
data Record =
Record { fields :: [Field] --
, rows :: [Entry] -- data
} deriving (Show,Generic)
instance FromJSON Record
getJSON :: IO ByteString
getJSON = BSL.readFile "json.txt"
main :: IO()
main = do
-- Get JSON data and decode it
dat <- (eitherDecode <$> getJSON) :: IO (Either String Record)
case dat of
Right parsed -> print parsed
Left err -> print err
我有一个 JSON 数据源,如下所示:
{ "fields": [
{ "type": "datetime",
"name": "Observation Valid",
"description": "Observation Valid Time"},
{ "type": "datetime",
"name": "Observation Valid UTC",
"description": "Observation Valid Time UTC"},
{ "type": "number",
"name": "Air Temperature[F]",
"description": "Air Temperature at 2m AGL"},
{ "type": "number",
"name": "Wind Speed[kt]",
"description": "Wind Speed"},
{ "type": "number",
"name": "Wind Gust[kt]",
"description": "Wind Gust"},
{ "type": "number", "name":
"Wind Direction[deg]",
"description": "Wind Direction"}
],
"rows": [
["2018-04-22T00:10:00", "2018-04-22T05:10:00Z", 50.0, 9.0, null, 50.0],
["2018-04-22T00:15:00", "2018-04-22T05:15:00Z", 50.0, 9.0, null, 60.0],
["2018-04-22T00:20:00", "2018-04-22T05:20:00Z", 50.0, 8.0, null, 60.0],
["2018-04-22T00:30:00", "2018-04-22T05:30:00Z", 50.0, 9.0, null, 60.0]
]
}
( https://mesonet.agron.iastate.edu/json/obhistory.py?station=TVK&network=AWOS&date=2018-04-22 )
并尝试了几种数据描述,最后是这样的:
data Entry = -- Data entries
Entry { time :: Text -- Observation Valid Time
, timeUTC :: Text -- Observation Valid Time UTC
, airTemp :: Float -- Air Temperature[F] at 2m AGL
, wind :: Float -- Wind Speed [kt]
, gust :: Float -- Wind Gust [kt]
, direction :: Int -- Wind Direction[deg]
} deriving (Show,Generic)
data Field = -- Schema Definition
Field { ftype :: String --
, name :: String --
, description :: String --
} deriving (Show,Generic)
data Record =
Record { fields :: [Field] --
, rows :: [Entry] -- data
} deriving (Show,Generic)
-- Instances to convert our type to/from JSON.
instance FromJSON Entry
instance FromJSON Field
instance FromJSON Record
-- Get JSON data and decode it
dat <- (eitherDecode <$> getJSON) :: IO (Either String Record)
这给出了这个错误: $.fields[0] 中的错误:键 "ftype" 不存在
(第一个)错误来自字段定义(我不使用)。在 JSON 中,条目是混合类型的数组,但在 Haskell 中,它只是一个数据结构,而不是数组——不确定如何协调这些。
毫无疑问是初学者的错误——但我还没有找到任何似乎具有这种结构的例子。我需要为此编写自定义解析器吗?
Field 有一个 ftype
字段,因此 AESON 试图在 JSON 中找到 ftype 但找不到(因为它包含 ftype)。我知道您不能在 Haskell 中命名字段 type
,因此您需要找到一种方法让 AESON 使用不同的名称。您需要使用模板 Haskell 并相应地设置 fieldLabelModifier。或者,手动编写坚持可能更简单。
三件事阻止了它按预期工作:
- JSON 数据包含一个名为 "type" 的字段。
Field
记录类型的自定义FromJson
实例可以处理此问题。 Entry
类型中的数据未命名,因此最好将其表示为不带字段名称的data
记录或tuple
。- 代表阵风的
Float
有时是null
所以应该是Maybe Float
下面的代码包含所有这些修改并解析您的示例 JSON 数据:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.ByteString.Lazy as BSL
import Data.Text (Text)
import Data.Aeson
import GHC.Generics
-- Either this tuple definition of Entry or the data definition without
-- names (commented out) will work.
type Entry = -- Data entries
( Text -- Observation Valid Time
, Text -- Observation Valid Time UTC
, Float -- Air Temperature[F] at 2m AGL
, Float -- Wind Speed [kt]
, Maybe Float -- Wind Gust [kt]
, Int -- Wind Direction[deg]
)
-- data Entry = -- Data entries
-- Entry Text -- Observation Valid Time
-- Text -- Observation Valid Time UTC
-- Float -- Air Temperature[F] at 2m AGL
-- Float -- Wind Speed [kt]
-- (Maybe Float) -- Wind Gust [kt]
-- Int -- Wind Direction[deg]
-- deriving (Show,Generic)
-- instance FromJSON Entry
data Field = -- Schema Definition
Field { ftype :: String --
, name :: String --
, description :: String --
} deriving (Show,Generic)
instance FromJSON Field where
parseJSON = withObject "Field" $ \v -> Field
<$> v .: "type"
<*> v .: "name"
<*> v .: "description"
data Record =
Record { fields :: [Field] --
, rows :: [Entry] -- data
} deriving (Show,Generic)
instance FromJSON Record
getJSON :: IO ByteString
getJSON = BSL.readFile "json.txt"
main :: IO()
main = do
-- Get JSON data and decode it
dat <- (eitherDecode <$> getJSON) :: IO (Either String Record)
case dat of
Right parsed -> print parsed
Left err -> print err