在 Haskell 中解析解码的 JSON
Parsing decoded JSON in Haskell
我需要一些帮助,了解如何在使用 Aeson 解码 JSON 后访问特定字段。
我想要一些关于访问字段或字段列表的最佳方式的提示。
例如下面的例子。数据包含 2 种产品的描述。我将如何 return 2 sku
字段即 ["ABCDEF","CDEFG"]
甚至如何访问两种产品的整个 Identity
数据类型。
我的输出是:
已解码 JSON
Just (Response {response = [Body {productId = 5555, brandId = 10, productTypeId = 1, identity = Identity {sku = "ABCDEF", ean = "1111", barcodls {taxable = False, taxCode = TaxCode {taxCodeId = 7, code = "T1"}}, variations = [Variation {optionId = 1, optionName = "option1", optionVal111221", barcode = "2443222"}, productGroupId = 17, stock = Stock {stockTracked = True, weight = Weight {magnitude = 0.0}, dimensions = Dimensption1", optionValueId = 5, optionValue = "5"},Variation {optionId = 2, optionName = "option2", optionValueId = 14, optionValue = "14"}]}]})
这里是原文JSON
JSON
{
"response": [
{
"id": 5555,
"brandId": 10,
"productTypeId": 1,
"identity": {
"sku": "ABCDEF",
"ean": "1111",
"barcode": "2222"
},
"productGroupId": 17,
"stock": {
"stockTracked": true,
"weight": {
"magnitude": 0
},
"dimensions": {
"length": 0,
"height": 0,
"width": 0,
"volume": 0
}
},
"financialDetails": {
"taxable": false,
"taxCode": {
"id": 7,
"code": "T1"
}
},
"variations": [
{
"optionId": 1,
"optionName": "option1",
"optionValueId": 5,
"optionValue": "5"
},
{
"optionId": 2,
"optionName": "option2",
"optionValueId": 14,
"optionValue": "OS"
}
]
},
{
"id": 9999,
"brandId": 10,
"productTypeId": 1,
"identity": {
"sku": "CDEFG",
"ean": "111221",
"barcode": "2443222"
},
"productGroupId": 17,
"stock": {
"stockTracked": true,
"weight": {
"magnitude": 0
},
"dimensions": {
"length": 0,
"height": 0,
"width": 0,
"volume": 0
}
},
"financialDetails": {
"taxable": false,
"taxCode": {
"id": 7,
"code": "T1"
}
},
"variations": [
{
"optionId": 1,
"optionName": "option1",
"optionValueId": 5,
"optionValue": "5"
},
{
"optionId": 2,
"optionName": "option2",
"optionValueId": 14,
"optionValue": "14"
}
]
}
]
}
到目前为止,这是我的代码:
代码
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Control.Applicative
import qualified Data.ByteString.Lazy.Char8 as BS
jsonFile :: FilePath
jsonFile = "test.json"
getJSON :: IO BS.ByteString
getJSON = BS.readFile jsonFile
main :: IO ()
main = do
input <- getJSON
let json = decode input :: Maybe Response
case json of
Nothing -> print "error parsing JSON"
Just m -> print json
data Response = Response
{ response :: [Body]
} deriving (Show)
instance FromJSON Response where
parseJSON (Object v) = Response <$> v .: "response"
parseJSON _ = mempty
data Body = Body
{ productId :: Int
, brandId :: Int
, productTypeId :: Int
, identity :: Identity
, productGroupId :: Int
, stock :: Stock
, financialDetails :: FinancialDetails
, variations :: [Variation]
} deriving (Show)
instance FromJSON Body where
parseJSON (Object v) = Body
<$> v .: "id"
<*> v .: "brandId"
<*> v .: "productTypeId"
<*> v .: "identity"
<*> v .: "productGroupId"
<*> v .: "stock"
<*> v .: "financialDetails"
<*> v .: "variations"
parseJSON _ = mempty
data Identity = Identity
{ sku :: String
, ean :: String
, barcode :: String
} deriving (Show)
instance FromJSON Identity where
parseJSON (Object v) = Identity
<$> v .: "sku"
<*> v .: "ean"
<*> v .: "barcode"
parseJSON _ = mempty
data Stock = Stock
{ stockTracked :: Bool
, weight :: Weight
, dimensions :: Dimensions
} deriving (Show)
instance FromJSON Stock where
parseJSON (Object v) = Stock
<$> v .: "stockTracked"
<*> v .: "weight"
<*> v .: "dimensions"
parseJSON _ = mempty
data Weight = Weight
{ magnitude :: Double
} deriving (Show)
instance FromJSON Weight where
parseJSON (Object v) = Weight
<$> v .: "magnitude"
parseJSON _ = mempty
data Dimensions = Dimensions
{ length :: Double
, height :: Double
, width :: Double
, volume :: Double
} deriving (Show)
instance FromJSON Dimensions where
parseJSON (Object v) = Dimensions
<$> v .: "length"
<*> v .: "height"
<*> v .: "width"
<*> v .: "volume"
parseJSON _ = mempty
data FinancialDetails = FinancialDetails
{ taxable :: Bool
, taxCode :: TaxCode
} deriving (Show)
instance FromJSON FinancialDetails where
parseJSON (Object v) = FinancialDetails
<$> v .: "taxable"
<*> v .: "taxCode"
parseJSON _ = mempty
data TaxCode = TaxCode
{ taxCodeId :: Int
, code :: String
} deriving (Show)
instance FromJSON TaxCode where
parseJSON (Object v) = TaxCode
<$> v .: "id"
<*> v .: "code"
parseJSON _ = mempty
data Variation = Variation
{ optionId :: Int
, optionName :: String
, optionValueId :: Int
, optionValue :: String
} deriving (Show)
instance FromJSON Variation where
parseJSON (Object v) = Variation
<$> v .: "optionId"
<*> v .: "optionName"
<*> v .: "optionValueId"
<*> v .: "optionValue"
parseJSON _ = mempty
对于这个问题,我会求助于 lens
库。虽然这是一个相当大的依赖关系,但它对于深入了解您的数据非常有用。请注意,lens
是一个由大量数学驱动的野兽,通常是外行人无法理解的,但在很多情况下,它是 "just works" 中的一个库。为了使其更容易工作(即不必自己编写所有镜头),您需要 TemplateHaskell
扩展名,并且您需要更改所有类型,以便它们的字段以下划线开头,所以
data Response = Response
{ _response :: [Body]
} deriving (Show)
其他类型依此类推。不过,您的 JSON 解析会很好。
那么您只需要导入 Control.Lens
。需要注意的是,它会导出一种名为 Identity
的类型,该类型会与您的类型发生冲突,因此
import Control.Lens hiding (Identity)
然后在文件的底部放置以下行
makeLenses ''Response
makeLenses ''Body
makeLenses ''Identity
makeLenses ''Stock
makeLenses ''Weight
makeLenses ''Dimensions
makeLenses ''FinancialDetails
makeLenses ''TaxCode
makeLenses ''Variation
这使用模板haskell 为您生成一堆功能,称为镜头。这些有一个我现在不会讨论的奇怪类型(那里有 plenty of tutorials),但它们都将与您的字段名称相同,但减去下划线。
这些新功能让您可以做一些非常疯狂的事情。例如,如果您想要在响应中包含所有 sku
,您只需执行
> -- Unwrap the Maybe here
> Just json <- decode <$> getJSON
> toListOf (response.traversed.identity.sku) json
["ABCDEF", "CDEFG"]
> -- Or as an alternative to toListOf you can use the operator ^..
> json^..response.traversed.identity.sku
["ABCDEF", "CDEFG"]
如果你只想访问一个字段,你可以使用 ^.
运算符,或者如果你想索引到一个列表中,你可能还需要 ^?
运算符,它允许安全索引(returns Nothing
如果你越界)
> json^?response.ix 0.identity
Just (Identity {_sku="ABCDEF", _ean="111", _barcode="222"})
> let Just ident = json^?response.ix 0.identity
> ident^.sku
"ABCDEF"
当然,您可以在不求助于 lens
的情况下完成所有这些操作。它可能看起来像
allSkus :: Response -> [String]
allSkus r = map (_sku . _identity) $ _response r
还不错,但是如果您想更改第一个标识中的值怎么办?使用 lens
您也可以执行集合(显然仍然使用不可变值):
> let newjson = (response.ix 0.identity.sku .~ "FOOBAR") json
> newjson^..response.traversed.identity.sku
["FOOBAR", "CDEFG"]
这是为了说明 lens
有多么强大。这是一个复杂的框架,更多的是它自己的语言 Haskell 并且需要一段时间来学习,但它可以很好地表达你想做什么而不是如何去做。
我需要一些帮助,了解如何在使用 Aeson 解码 JSON 后访问特定字段。
我想要一些关于访问字段或字段列表的最佳方式的提示。
例如下面的例子。数据包含 2 种产品的描述。我将如何 return 2 sku
字段即 ["ABCDEF","CDEFG"]
甚至如何访问两种产品的整个 Identity
数据类型。
我的输出是:
已解码 JSON
Just (Response {response = [Body {productId = 5555, brandId = 10, productTypeId = 1, identity = Identity {sku = "ABCDEF", ean = "1111", barcodls {taxable = False, taxCode = TaxCode {taxCodeId = 7, code = "T1"}}, variations = [Variation {optionId = 1, optionName = "option1", optionVal111221", barcode = "2443222"}, productGroupId = 17, stock = Stock {stockTracked = True, weight = Weight {magnitude = 0.0}, dimensions = Dimensption1", optionValueId = 5, optionValue = "5"},Variation {optionId = 2, optionName = "option2", optionValueId = 14, optionValue = "14"}]}]})
这里是原文JSON
JSON
{
"response": [
{
"id": 5555,
"brandId": 10,
"productTypeId": 1,
"identity": {
"sku": "ABCDEF",
"ean": "1111",
"barcode": "2222"
},
"productGroupId": 17,
"stock": {
"stockTracked": true,
"weight": {
"magnitude": 0
},
"dimensions": {
"length": 0,
"height": 0,
"width": 0,
"volume": 0
}
},
"financialDetails": {
"taxable": false,
"taxCode": {
"id": 7,
"code": "T1"
}
},
"variations": [
{
"optionId": 1,
"optionName": "option1",
"optionValueId": 5,
"optionValue": "5"
},
{
"optionId": 2,
"optionName": "option2",
"optionValueId": 14,
"optionValue": "OS"
}
]
},
{
"id": 9999,
"brandId": 10,
"productTypeId": 1,
"identity": {
"sku": "CDEFG",
"ean": "111221",
"barcode": "2443222"
},
"productGroupId": 17,
"stock": {
"stockTracked": true,
"weight": {
"magnitude": 0
},
"dimensions": {
"length": 0,
"height": 0,
"width": 0,
"volume": 0
}
},
"financialDetails": {
"taxable": false,
"taxCode": {
"id": 7,
"code": "T1"
}
},
"variations": [
{
"optionId": 1,
"optionName": "option1",
"optionValueId": 5,
"optionValue": "5"
},
{
"optionId": 2,
"optionName": "option2",
"optionValueId": 14,
"optionValue": "14"
}
]
}
]
}
到目前为止,这是我的代码:
代码
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Control.Applicative
import qualified Data.ByteString.Lazy.Char8 as BS
jsonFile :: FilePath
jsonFile = "test.json"
getJSON :: IO BS.ByteString
getJSON = BS.readFile jsonFile
main :: IO ()
main = do
input <- getJSON
let json = decode input :: Maybe Response
case json of
Nothing -> print "error parsing JSON"
Just m -> print json
data Response = Response
{ response :: [Body]
} deriving (Show)
instance FromJSON Response where
parseJSON (Object v) = Response <$> v .: "response"
parseJSON _ = mempty
data Body = Body
{ productId :: Int
, brandId :: Int
, productTypeId :: Int
, identity :: Identity
, productGroupId :: Int
, stock :: Stock
, financialDetails :: FinancialDetails
, variations :: [Variation]
} deriving (Show)
instance FromJSON Body where
parseJSON (Object v) = Body
<$> v .: "id"
<*> v .: "brandId"
<*> v .: "productTypeId"
<*> v .: "identity"
<*> v .: "productGroupId"
<*> v .: "stock"
<*> v .: "financialDetails"
<*> v .: "variations"
parseJSON _ = mempty
data Identity = Identity
{ sku :: String
, ean :: String
, barcode :: String
} deriving (Show)
instance FromJSON Identity where
parseJSON (Object v) = Identity
<$> v .: "sku"
<*> v .: "ean"
<*> v .: "barcode"
parseJSON _ = mempty
data Stock = Stock
{ stockTracked :: Bool
, weight :: Weight
, dimensions :: Dimensions
} deriving (Show)
instance FromJSON Stock where
parseJSON (Object v) = Stock
<$> v .: "stockTracked"
<*> v .: "weight"
<*> v .: "dimensions"
parseJSON _ = mempty
data Weight = Weight
{ magnitude :: Double
} deriving (Show)
instance FromJSON Weight where
parseJSON (Object v) = Weight
<$> v .: "magnitude"
parseJSON _ = mempty
data Dimensions = Dimensions
{ length :: Double
, height :: Double
, width :: Double
, volume :: Double
} deriving (Show)
instance FromJSON Dimensions where
parseJSON (Object v) = Dimensions
<$> v .: "length"
<*> v .: "height"
<*> v .: "width"
<*> v .: "volume"
parseJSON _ = mempty
data FinancialDetails = FinancialDetails
{ taxable :: Bool
, taxCode :: TaxCode
} deriving (Show)
instance FromJSON FinancialDetails where
parseJSON (Object v) = FinancialDetails
<$> v .: "taxable"
<*> v .: "taxCode"
parseJSON _ = mempty
data TaxCode = TaxCode
{ taxCodeId :: Int
, code :: String
} deriving (Show)
instance FromJSON TaxCode where
parseJSON (Object v) = TaxCode
<$> v .: "id"
<*> v .: "code"
parseJSON _ = mempty
data Variation = Variation
{ optionId :: Int
, optionName :: String
, optionValueId :: Int
, optionValue :: String
} deriving (Show)
instance FromJSON Variation where
parseJSON (Object v) = Variation
<$> v .: "optionId"
<*> v .: "optionName"
<*> v .: "optionValueId"
<*> v .: "optionValue"
parseJSON _ = mempty
对于这个问题,我会求助于 lens
库。虽然这是一个相当大的依赖关系,但它对于深入了解您的数据非常有用。请注意,lens
是一个由大量数学驱动的野兽,通常是外行人无法理解的,但在很多情况下,它是 "just works" 中的一个库。为了使其更容易工作(即不必自己编写所有镜头),您需要 TemplateHaskell
扩展名,并且您需要更改所有类型,以便它们的字段以下划线开头,所以
data Response = Response
{ _response :: [Body]
} deriving (Show)
其他类型依此类推。不过,您的 JSON 解析会很好。
那么您只需要导入 Control.Lens
。需要注意的是,它会导出一种名为 Identity
的类型,该类型会与您的类型发生冲突,因此
import Control.Lens hiding (Identity)
然后在文件的底部放置以下行
makeLenses ''Response
makeLenses ''Body
makeLenses ''Identity
makeLenses ''Stock
makeLenses ''Weight
makeLenses ''Dimensions
makeLenses ''FinancialDetails
makeLenses ''TaxCode
makeLenses ''Variation
这使用模板haskell 为您生成一堆功能,称为镜头。这些有一个我现在不会讨论的奇怪类型(那里有 plenty of tutorials),但它们都将与您的字段名称相同,但减去下划线。
这些新功能让您可以做一些非常疯狂的事情。例如,如果您想要在响应中包含所有 sku
,您只需执行
> -- Unwrap the Maybe here
> Just json <- decode <$> getJSON
> toListOf (response.traversed.identity.sku) json
["ABCDEF", "CDEFG"]
> -- Or as an alternative to toListOf you can use the operator ^..
> json^..response.traversed.identity.sku
["ABCDEF", "CDEFG"]
如果你只想访问一个字段,你可以使用 ^.
运算符,或者如果你想索引到一个列表中,你可能还需要 ^?
运算符,它允许安全索引(returns Nothing
如果你越界)
> json^?response.ix 0.identity
Just (Identity {_sku="ABCDEF", _ean="111", _barcode="222"})
> let Just ident = json^?response.ix 0.identity
> ident^.sku
"ABCDEF"
当然,您可以在不求助于 lens
的情况下完成所有这些操作。它可能看起来像
allSkus :: Response -> [String]
allSkus r = map (_sku . _identity) $ _response r
还不错,但是如果您想更改第一个标识中的值怎么办?使用 lens
您也可以执行集合(显然仍然使用不可变值):
> let newjson = (response.ix 0.identity.sku .~ "FOOBAR") json
> newjson^..response.traversed.identity.sku
["FOOBAR", "CDEFG"]
这是为了说明 lens
有多么强大。这是一个复杂的框架,更多的是它自己的语言 Haskell 并且需要一段时间来学习,但它可以很好地表达你想做什么而不是如何去做。