2015-12-04 105 views
2

我想要一些解決JSON與Aeson解碼時如何訪問特定字段的幫助。在Haskell中解析解碼的JSON

我想了解一下當我處於這種狀態時訪問字段或字段列表的最佳方式的一些提示。

例如從下面。數據包含2種產品的說明。我將如何返回2 sku字段,即["ABCDEF","CDEFG"]甚至如何訪問兩個產品的整個Identity數據類型。

我的輸出是:

DECODED 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" 
     } 
     ] 
    } 
    ] 
} 

這裏是我到目前爲止的代碼:

CODE

{-# 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 

回答

2

對於這個問題,我會求助於lens庫。雖然這是一個相當大的依賴關係,但對於深入研究數據可能非常有用。被警告,lens是一種由大量數學驅動的野獸,通常超出了非專業人士,但在很多情況下,它是「正常工作」的圖書館之一。爲了讓工作輕鬆(即不必親自編寫所有的鏡頭)您需要的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 

這將使用模板哈斯克爾生成一串爲你的功能稱爲鏡頭。這些有一個奇怪的類型,我現在不會進入(這裏有plenty of tutorials),但它們都將與您的字段名稱減去下劃線相同。

這些新功能可以讓你做一些非常瘋狂的事情。例如,如果你想所有的sku s的響應裏面你可以做

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

如果你只是想訪問一個字段,你可以使用^.運營商,或者如果你想索引到一個列表你「會大概也是希望^?運營商允許安全索引(如果你去出界返回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內部使用它自己的語言,它需要一段時間才能學會,但它可以很好地表達你想要做什麼,而不是如何去做。

+0

這很棒,你的解釋非常清楚。再次感謝。 – matthias