2015-07-15 6 views
5

J'ai passé du temps à jouer avec Aeson, mais je n'arrive pas à obtenir des types de données algébriques pour sérialiser correctement.Comment sérialiser les ADT Haskell en tant que JSON ordonné, en utilisant Aeson?

Ce que j'ai essayé est:

data Attach = Attach { tel :: String } 
       deriving (Show) 
$(deriveJSON defaultOptions ''Attach) 

data Fix = Fix { lat :: Double, lng :: Double } 
       deriving (Show) 
$(deriveJSON defaultOptions ''Fix) 

data MsgIn = AttachMsg Attach 
      | FixMsg Fix 
      deriving (Show) 
$(deriveJSON defaultOptions ''MsgIn) 

data MsgIn2 = MsgIn2 { attach :: Maybe Attach, fix :: Maybe Fix } 
      deriving (Show) 
$(deriveJSON defaultOptions ''MsgIn2) 

someFunc :: IO() 
someFunc = do 
    let attach = Attach "+447890" 
    let reply = AttachMsg attach 
    BL.putStrLn (encode reply) 
    let reply2 = MsgIn2 (Just attach) Nothing 
    BL.putStrLn (encode reply2) 

La sortie est:

{"tag":"AttachMsg","contents":{"tel":"+447890"}} 
{"attach":{"tel":"+447890"},"fix":null} 

La sortie Je cherche est:

{"attach":{"tel":"+447890"}} 

mais à partir du type MsgIn plutôt que MsgIn2.

(La sortie de MsgIn2 obtient assez proche, mais il a une null explicite.)

Est-il possible de le faire en Aeson?


Mise à jour:

J'ajouté:

instance ToJSON MsgIn3 where 
    toJSON (AttachMsg3 (Attach tel)) = object ["attach" .= object ["tel" .= tel]] 
... 
let reply3 = AttachMsg3 attach 
BL.putStrLn (encode reply3) 

et a obtenu la réponse que je voulais: {"attach":{"tel":"+447890"}}.

@bheklilr existe-t-il un moyen d'utiliser la sérialisation de Attach (déjà définie) au lieu de la définir à nouveau?

J'ai essayé une syntaxe non-sens, mais compréhensible, il ne compile pas:

instance ToJSON MsgIn3 where 
    toJSON (AttachMsg3 (Attach tel)) = object ["attach" .= (toJSON :: Attach)] 
+2

Vous pouvez simplement écrire les instances 'ToJSON' et' FromJSON' manuellement pour obtenir exactement ce que vous voulez: 'toJSON (AttachMsg (Attach tel)) = objet [" attacher ". = Objet [" tel ". = Tel] ] ', et de même pour' FixMsg'. L'implémentation 'parseJSON' ne serait pas beaucoup plus difficile. – bheklilr

+0

@bheklilr Merci, c'était vraiment utile. Pourriez-vous en faire une réponse?Pourriez-vous voir ma question mise à jour ... – fadedbee

Répondre

5

Utilisez les options personnalisées au lieu de DefaultOptions. Vous pouvez obtenir la bonne structure en utilisant sumEncoding = ObjectWithSingleField, ce qui réduit votre premier exemple à {"AttachMsg":{"tel":"+447890"}}. Vous pouvez ensuite personnaliser les balises constructeur en utilisant constructorTagModifier = myConstructorTag et en écrivant une fonction myConstructorTag qui personnalise les noms à votre convenance (par exemple, AttachMsg -> attach).

À titre d'exemple, vous obtiendrez la sortie que vous voulez en écrivant ceci dans un module distinct, importer et utiliser myOptions au lieu de defaultOptions:

myConstructorTag :: String -> String 
myConstructorTag "AttachMsg" = "attach" 
myConstructorTag x = x 

myOptions :: Options 
myOptions = defaultOptions {sumEncoding = ObjectWithSingleField, constructorTagModifier = myConstructorTag} 

Un module séparé est nécessaire ici à cause de modèle Haskell. Il y a probablement un moyen de définir myConstructorTag et myOptions de manière à satisfaire les besoins de TH, mais je n'ai absolument aucune idée de comment faire cela.

+0

Merci, cela ressemble à la bonne réponse - il se fait tard, alors je vais essayer demain. – fadedbee

+0

Fonctionne parfaitement - le fichier séparé pour définir les options n'est pas un problème. – fadedbee

4

Vous pouvez obtenir Aeson sauter les champs null automatiquement. Je fais habituellement ceci en combinaison avec l'DeriveGeneric extension:

{-# LANGUAGE OverloadedStrings, DeriveGeneriC#-} 

import Data.Aeson 
import Data.Aeson.Types 
import qualified Data.ByteString.Lazy.Char8 as BL 
import GHC.Generics 

data Attach = Attach { tel :: String } deriving (Show, Generic) 

data Fix = Fix { lat :: Double, lng :: Double } deriving (Show, Generic) 

data Msg = Msg { attach :: Attach, fix :: Maybe Fix } deriving (Show, Generic) 

instance ToJSON Attach 
instance ToJSON Fix 
instance ToJSON Msg where 
    toJSON = genericToJSON (defaultOptions { omitNothingFields = True }) 

main = do 
    let attach = Attach "+447890" 
     reply = Msg attach Nothing 
    BL.putStrLn (encode reply) 

qui vous donne:

*Main> main 
{"attach":{"tel":"+447890"}} 
+0

Merci pour votre réponse, mais je ne me suis pas expliqué assez clairement. Je veux la sortie '{" attacher ": {" tel ":" + 447890 "}}' du type de données original 'MsgIn'. – fadedbee

+1

C'est toujours une information utile, même si elle ne répond pas à la question, donc je l'upvote de toute façon. – Carl

+0

Oui, j'ai aussi upvoted car c'est utile. – fadedbee