Automatic ToJSON output for (Map NewtypeOfText v)

I have a Map where the key is a new type of text. I would automatically (as much as possible) get ToJSON and FromJSON for this Map. aeson already has instances for ToJSON and FromJSON for Map Text v.

My verbose code that works:

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

Can I do something more like the following?

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

Edit

I tried (but still no luck):

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

and

 newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord, ToJSON, FromJSON) instance ToJSON Foo 
+6
source share
1 answer

The fact that you cannot automatically get this instance is 100% correct behavior. The reason that what you expect does not work is that there is no way to find out that an FromJSON (Map Text v) instance FromJSON (Map Text v) can be used for values ​​like Map MyText v . This is because the creation and manipulation of Map based on the Ord instance of its key, and there is no way (for the compiler) to know that for all xy (x == y) == (MyText x == MyText y) , which are necessary for Map MyText v Map Text v to Map MyText v . More technically, a Map role declaration:

 type role Map nominal representational 

In fact, this suggests that Map kv is only coercible for other maps, the first parameter of which is identical. Vicki says:

we have an instance of Coercible ab => Coercible (T a) (T b) if and only if the first parameter has a representative role.

The Coercible class Coercible used to create safe enforcement types in recent versions of GHC (7.8?). For more information on type roles and their role in type safety, see here .

If you plan to output an instance for Ord MyText , it is really safe to force Map Text v to Map MyText v , since the Ord instance is the same. This requires the use of unsafeCoerce . You should still write the instance yourself:

 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 

If you plan to write your own copy of Ord , this is completely unsafe. Your decision is correct, but not very effective. Use the following:

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

Depending on your Ord instance, you might use mapKeysMonotonic instead, which would be more efficient. See the Data.Map Documentation exactly when you can use mapKeysMonotonic .

Then the obvious things will work:

 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) 

Full code:

 {-# 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) 
+8
source

Source: https://habr.com/ru/post/976960/


All Articles