7

キーが新しいタイプのテキストであるマップがあります。このマップのToJSONFromJSONを自動的に (可能な限り) 派生させたいと考えています。aesonには、マップ テキスト v の ToJSON および FromJSON のインスタンスが既にあります。

動作する私の詳細なコード:

{-# LANGUAGE DeriveGeneric    #-}

module Test where

import           ClassyPrelude

import           Data.Aeson                  
import           GHC.Generics                (Generic)

import qualified Data.Map                    as M

newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord)

data Bar = Bar deriving (Generic)
instance ToJSON Bar
instance FromJSON Bar

data Foo = Foo (Map MyText Bar)

instance ToJSON Foo where
  toJSON (Foo x) = toJSON mp
    where mp = M.fromList . map (\(x,y) -> (unMyText x,y)) . M.toList $ x

instance FromJSON Foo where
  parseJSON v = convert <$> parseJSON v
    where convert :: Map Text Bar -> Foo
          convert =  Foo . mapFromList . map (\(x,y) -> (MyText x,y)) . mapToList

次のようなことはできますか?

data Foo = Foo (Map MyText Bar) deriving (Generic)

instance ToJSON Foo 
instance FromJSON Foo

編集

私は試しました(しかし、まだ運がありません):

newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord, ToJSON, FromJSON)
instance ToJSON Foo where
  toJSON (Foo x) = toJSON x

newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord, ToJSON, FromJSON)
instance ToJSON Foo
4

1 に答える 1

8

このインスタンスを自動的に導出できないという事実は、100% 正しい動作です。期待どおりに動作しない理由は、インスタンスFromJSON (Map Text v)が type の値で使用できることを知る方法がないためMap MyText vです。これは、 a の作成と操作がそのキーMapのインスタンスに基づいており、Ord(コンパイラにとって) すべての xy(x == y) == (MyText x == MyText y)についてそれを知る方法がないMap Text vためMap MyText vです。より技術的には、のロール宣言は次のMapとおりです。

type role Map nominal representational

基本的に、これはMap k v、最初の型パラメーターが同一である他のマップに対してのみ強制可能であることを示しています。ウィキには次のように書かれています。

インスタンス Coercible ab => Coercible (T a) (T b) があるのは、最初のパラメーターが表現の役割を持っている場合のみです。

このクラスCoercibleは、GHC の最近のバージョン (7.8?) で型安全な強制を行うために使用ます。の

のインスタンスを派生させる予定がある場合は、インスタンスが同じであるため、に強制してOrd MyTextも安全です。これには を使用する必要があります。ただし、インスタンスを自分で作成する必要があります。Map Text vMap MyText vOrdunsafeCoerce

instance ToJSON v => ToJSON (Map MyText v) where
  toJSON = toJSON . (unsafeCoerce :: Map MyText v -> Map Text v)

instance FromJSON v => FromJSON (Map MyText v) where 
  parseJSON = (unsafeCoerce :: Parser (Map Text v) -> Parser (Map MyText v)) . parseJSON 

独自のインスタンスを作成する予定がOrdある場合、上記は絶対に安全ではありません。あなたの解決策は正しいですが、あまり効率的ではありません。以下を使用します。

  toJSON = toJSON . M.mapKeys (coerce :: MyText -> Text)
  parseJSON = fmap (M.mapKeys (coerce :: Text -> MyText)) . parseJSON

mapKeysMonotonicOrd インスタンスによっては、より効率的な代わりに使用できる場合があります。Data.Mapをいつ使用できるかについては、 のドキュメントを参照してくださいmapKeysMonotonic

次に、明らかなことが機能します。

data Bar = Bar deriving (Eq, Ord, Generic)
instance ToJSON Bar
instance FromJSON Bar

data Foo = Foo (Map MyText Bar) deriving (Generic)
instance ToJSON Foo 
instance FromJSON Foo

-- Using GeneralizedNewtypeDeriving
newtype Foo2 = Foo2 (Map MyText Bar) deriving (FromJSON, ToJSON)

完全なコード:

{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving, FlexibleInstances #-}

module Test where

import Data.Aeson                  
import GHC.Generics (Generic)
import qualified Data.Map as M
import Data.Map (Map)
import Data.Text (Text)
import GHC.Prim (coerce)
import Unsafe.Coerce (unsafeCoerce)
import Data.Aeson.Types (Parser)

newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord, Generic, ToJSON, FromJSON)

instance ToJSON v => ToJSON (Map MyText v) where
  -- toJSON = toJSON . M.mapKeys (coerce :: MyText -> Text)
  toJSON = toJSON . (unsafeCoerce :: Map MyText v -> Map Text v)

instance FromJSON v => FromJSON (Map MyText v) where 
  -- parseJSON x = fmap (M.mapKeys (coerce :: Text -> MyText)) (parseJSON x)
  parseJSON x = (unsafeCoerce :: Parser (Map Text v) -> Parser (Map MyText v)) (parseJSON x)

data Bar = Bar deriving (Eq, Ord, Generic)
instance ToJSON Bar
instance FromJSON Bar

data Foo = Foo (Map MyText Bar) deriving (Generic)
instance ToJSON Foo 
instance FromJSON Foo

newtype Foo2 = Foo2 (Map MyText Bar) deriving (FromJSON, ToJSON)
于 2014-10-20T10:07:23.750 に答える