2013-07-24 33 views
10

Tôi đang cố gắng phân tích cú pháp dữ liệu JSON trong haskell. Đã trải qua một loạt các trang web, đây là điều xa nhất mà tôi có thể truy cập.phân tích cú pháp json trong haskell

data Address = Address { house :: Integer, street :: String, city :: String, state :: String, zip :: Integer } deriving (Show) 
data Person = Person { name :: String, age :: Integer, address :: Address } deriving (Show) 

getName :: Person -> String 
getName (Person n _ _) = n 

getAddress :: Person -> Address 
getAddress (Person _ _ a) = a 

getState :: Address -> String 
getState (Address _ _ _ s _) = s 

tôi viết rằng trong một tập tin ex.hs và tải nó trong ghci ->

Prelude> import Text.JSON 
Prelude Text.JSON> :load ex 
Main Text.JSON> let aa = "{\"name\": \"some body\", \"age\" : 23, \"address\" : {\"house\" : 285, \"street\" : \"7th Ave.\", \"city\" : \"New York\", \"state\" : \"New York\", \"zip\" : 10001}}" 
...> decode aa :: Result JSValue 

Nó trả

Ok (JSObject (JSONObject {fromJSObject = [("name",JSString (JSONString {fromJSString = "some body"})),("age",JSRational False (23 % 1)),("address",JSObject (JSONObject {fromJSObject = [("house",JSRational False (285 % 1)),("street",JSString (JSONString {fromJSString = "7th Ave."})),("city",JSString (JSONString {fromJSString = "New York"})),("state",JSString (JSONString {fromJSString = "New York"})),("zip",JSRational False (10001 % 1))]}))]})) 

Không cần phải nói, có vẻ như khá dài dòng (và khủng khiếp). Tôi đã thử làm

...> decode aa :: Result Person 

và nó đã cho tôi một lỗi. Làm thế nào để tôi đi về populating một thể hiện của cơ sở hạ tầng Person từ chuỗi json này? Ví dụ: tôi nên làm gì để nhận trạng thái của người trong chuỗi JSON ...

Trả lời

22

Vấn đề là Text.JSON không biết cách chuyển đổi JSON dữ liệu thành loại dữ liệu Person của bạn. Để thực hiện việc này, bạn cần phải thực hiện ví dụ Person và của kiểu chữ JSON, hoặc bạn có thể sử dụng Text.JSON.Generic và tiện ích mở rộng DeriveDataTypeable để thực hiện công việc cho bạn.

Generics

Phương pháp Text.JSON.Generic sẽ đọc cấu trúc JSON dựa trên cấu trúc của kiểu dữ liệu của bạn.

{-# LANGUAGE DeriveDataTypeable #-} 
import   Text.JSON.Generic 

data Address = Address 
    { house :: Integer 
    , street :: String 
    , city :: String 
    , state :: String 
    , zip :: Integer 
    } deriving (Show, Data, Typeable) 

data Person = Person 
    { name :: String 
    , age  :: Integer 
    , address :: Address 
    } deriving (Show, Data, Typeable) 

aa :: String 
aa = "{\"name\": \"some body\", \"age\" : 23, \"address\" : {\"house\" : 285, \"street\" : \"7th Ave.\", \"city\" : \"New York\", \"state\" : \"New York\", \"zip\" : 10001}}" 

main = print (decodeJSON aa :: Person) 

Phương pháp này hoạt động thực sự tốt miễn là bạn không quan tâm phù hợp với tên của các lĩnh vực trong cấu trúc dữ liệu của bạn sang định dạng JSON của bạn.

Ngoài ra, bạn không cần phải viết các chức năng như getName, getAddress, và getState. Tên của trường trong loại bản ghi của bạn là accesor chức năng.

∀ x. x ⊦ :t house 
house :: Address -> Integer 
∀ x. x ⊦ :t address 
address :: Person -> Address 

JSON Instance

Ngoài ra, bạn có thể đi theo đường cao và thực hiện ví dụ của riêng bạn lớp JSON.

import   Control.Applicative 
import   Control.Monad 
import   Text.JSON 

data Address = Address 
    { house :: Integer 
    , street :: String 
    , city :: String 
    , state :: String 
    -- Renamed so as not to conflict with zip from Prelude 
    , zipC :: Integer 
    } deriving (Show) 

data Person = Person 
    { name :: String 
    , age  :: Integer 
    , address :: Address 
    } deriving (Show) 

aa :: String 
aa = "{\"name\": \"some body\", \"age\" : 23, \"address\" : {\"house\" : 285, \"street\" : \"7th Ave.\", \"city\" : \"New York\", \"state\" : \"New York\", \"zip\" : 10001}}" 

-- For convenience 
(!) :: (JSON a) => JSObject JSValue -> String -> Result a 
(!) = flip valFromObj 

instance JSON Address where 
    -- Keep the compiler quiet 
    showJSON = undefined 

    readJSON (JSObject obj) = 
     Address  <$> 
     obj ! "house" <*> 
     obj ! "street" <*> 
     obj ! "city" <*> 
     obj ! "state" <*> 
     obj ! "zip" 
    readJSON _ = mzero 

instance JSON Person where 
    -- Keep the compiler quiet 
    showJSON = undefined 

    readJSON (JSObject obj) = 
     Person  <$> 
     obj ! "name" <*> 
     obj ! "age" <*> 
     obj ! "address" 
    readJSON _ = mzero 

main = print (decode aa :: Result Person) 

này có lợi thế của một thực tế là loại Result là một Applicative dễ dàng chuỗi lại với nhau truy vấn trên giá trị JSObject.

Đây là một công việc ít hơn, nhưng nó cung cấp cho bạn kiểm soát nhiều hơn về cấu trúc của các JSON nếu bạn phải đối phó với JSON rằng sẽ gây ra phong cách châm vi phạm do tên trường lạ.

+0

Có lẽ bạn cũng nên đưa ra một ví dụ về việc tạo một thể hiện của JSON vì bạn đã đề cập đến nó như một sự thay thế. – Wes

+0

@Wes, Có bạn đi. – sabauma

+0

Thông tin rất hữu ích. Tôi có một câu hỏi. Ngoài 'Text.JSON.Generic' (mà gói này đến từ đâu?), Tôi cũng đã tìm thấy https://hackage.haskell.org/package/generic-aeson tương tự sử dụng máy móc Generics để tạo các thể hiện JSON của Haskell dữ liệu. Sự khác nhau giữa hai gói này là gì? –

5

Có thể hơi muộn trong trò chơi, nhưng vì đây là trang đầu tiên google trả về, tôi sẽ cung cấp cho nó một đi.

Aeson là tiêu chuẩn defacto những ngày này để thư viện mà mọi người sử dụng. Gói Aeson TH cung cấp một số chức năng đẹp để tự động tạo các chức năng cần thiết cho các kiểu dữ liệu tùy chỉnh của bạn.

Về cơ bản, bạn tạo các kiểu dữ liệu tương ứng với dữ liệu json và sau đó để cho aeson thực hiện phép thuật.

{-# LANGUAGE OverloadedStrings,TemplateHaskell #-} 
import Data.Aeson 
import Data.Aeson.TH 
import qualified Data.ByteString.Lazy.Char8 as BL 

data Address = Address 
    { house :: Integer 
    , street :: String 
    , city :: String 
    , state :: Maybe String 
    , zip :: Integer 
    } deriving (Show, Eq) 

data Person = Person 
    { name :: String 
    , age  :: Integer 
    , address :: Address 
    } deriving (Show, Eq) 

$(deriveJSON defaultOptions ''Address) 
$(deriveJSON defaultOptions ''Person) 

aa :: BL.ByteString 
aa = "{\"name\": \"some body\", \"age\" : 23, \"address\" : {\"house\" : 285, \"street\" : \"7th Ave.\", \"city\" : \"New York\", \"state\" : \"New York\", \"zip\" : 10001}}" 

main = print (decode aa :: Maybe Person) 

Bạn thậm chí có thể có các trường tùy chọn với kiểu dữ liệu Maybe.

+0

Aeson phải liên kết nhiều thư viện hơn gói 'json' – dani24