4

私は Haskell を初めて使用し、命令型プログラミングのバックグラウンドを持っています。「Haskell の方法」でオブジェクトを JSON にシリアル化できるようにしたいのですが、その方法はまだよくわかりません。

JSON について少し説明しているRealWorldHaskell の第 5 章を読み、Aeson をいじりました。また、次のような Haskell で記述されたいくつかの JSON API ライブラリも調べました。

これにより、オブジェクトから非常に基本的な JSON 文字列を作成できるようになりました (このブログ投稿のおかげでもあります)。

{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}

import Data.Aeson
import GHC.Generics

data User = User {
  email :: String,
  name :: String
} deriving (Show, Generic)

instance ToJSON User

main = do
  let user = User "foo@example.com" "Hello World"
  let json = encode user
  putStrLn $ show json

それは印刷されます:

"{\"email\":\"foo@example.com",\"name\":\"Hello World\"}"

ここでの目標は、User任意のフィールドを持つことができるインスタンスに別のフィールドを追加することです。Facebook Graph API には、data必要なプロパティを持つ JSON オブジェクトである というフィールドがあります。たとえば、Facebook の API に対して次のようなリクエストを行うことができます (疑似コード、Facebook API に正確には詳しくありません)。

POST api.facebook.com/actions
{
  "name": "read",
  "object": "book",
  "data": {
    "favoriteChapter": 10,
    "hardcover": true
  }
}

最初の 2 つのフィールドnameobjectは typeStringですが、dataフィールドは任意のプロパティのマップです。

User問題は、上記のモデルでそれを達成するための「Haskell の方法」は何ですか?

単純なケースを行う方法を理解できます:

data User = User {
  email :: String,
  name :: String,
  data :: CustomData
} deriving (Show, Generic)

data CustomData = CustomData {
  favoriteColor :: String
}

しかし、それは私が探しているものではありません。つまり、UserJSON にシリアライズされると、型は常に次のようになります。

{
  "email": "",
  "name": "",
  "data": {
    "favoriteColor": ""
  }
}

User問題は、その型を 1 回定義するだけで、そのプロパティに任意のフィールドをアタッチできるようにするにはどうすればよいかということですdata。その一方で、静的型付け (またはそれに近いもので、詳細にあまり精通していないもの) の恩恵を受けます。タイプのまだ)。

4

2 に答える 2

4

それは、任意のデータが何を意味するかによって異なります。「データには任意のドキュメント タイプが含まれる」という合理的で自明ではない定義と思われるものを抽出し、いくつかの可能性を示します。

まず、私の過去のブログ記事を紹介します。これは、構造や性質が異なるドキュメントを解析する方法を示しています。既存の例: http://bitemyapp.com/posts/2014-04-17-parsing-nondeterministic-data-with-aeson-and-sum-types.html

データ型に適用すると、これは次のようになります。

data CustomData = NotesData Text | UserAge Int deriving (Show, Generic)
newtype Email = Email Text deriving (Show, Generic)
newtype Name  = Name  Text deriving (Show, Generic)

data User = User {
  email :: Email,
  name  :: Name,
  data  :: CustomData
} deriving (Show, Generic)

次に、より高い種類の型を使用してパラメーター化可能な構造を定義する方法を示します。既存の例: http://bitemyapp.com/posts/2014-04-11-aeson-and-user-created-types.html

newtype Email = Email Text deriving (Show, Generic)
newtype Name  = Name  Text deriving (Show, Generic)

-- 'a' needs to implement ToJSON/FromJSON as appropriate
data User a = User {
  email :: Email,
  name  :: Name,
  data  :: a
} deriving (Show, Generic)

上記のコードで、パラメータdataUserしてより高い種類の型を作成しました。User型引数の型によってパラメータ化された構造化が行われるようになりました。フィールドは、 with 、文字列、数値などのdataドキュメントにすることができます。おそらく、Int/String ではなく、意味的に意味のある型が必要です。これを行うには、必要に応じて newtype を使用してください。User CustomDataUser TextUser Int

多くの人が (Double、Double) としてエンコードするデータ型に構造と意味を与える方法のかなり詳細な例については、https://github.com/NICTA/coordinateを参照してください。

適切と思われる場合は、これらのアプローチを組み合わせることができます。囲んでいるドキュメントへの型引数で、型が特定の単一の可能性を表現できるようにするかどうかによって部分的に異なります。

https://github.com/bitemyapp/bloodhoundのライブラリに大量の JSON 処理コードとデータを構造化する方法の例があります。

指針となる原則は、無効なデータを型によって可能な限り表現できないようにすることです。型だけではデータを検証できない場合は、「スマート コンストラクター」の使用を検討してください。

スマート コンストラクターの詳細については、https ://www.haskell.org/haskellwiki/Smart_constructors を参照してください。

于 2014-11-02T02:18:18.973 に答える
2

Aeson の FromJSON クラスで完全に任意の JSON サブ構造を本当に受け入れたい場合は、フィールド user::Value を作成することをお勧めします。これは、任意の JSON 値に対する Aeson のジェネリック型です。後でこの JSON 値の可能なタイプを見つけた場合は、FromJSON を使用して再度変換できますが、最初はそこにあるものはすべて保持されます。

于 2014-11-02T08:32:59.703 に答える