2015-07-15 10 views
5

Ho passato un po 'di tempo a giocare con Aeson, ma non riesco a far serializzare correttamente i tipi di dati algebrici.Come serializzare gli ADT Haskell come ordinato JSON, usando Aeson?

Quello che ho provato è:

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) 

l'output è:

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

L'uscita che sto cercando è:

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

ma dal tipo MsgIn , piuttosto che MsgIn2.

(L'uscita del MsgIn2 ottiene abbastanza vicino, ma ha un esplicito null.)

C'è un modo di fare questo in Aeson?


Aggiornamento:

ho aggiunto:

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

ed ha ottenuto la risposta che volevo: {"attach":{"tel":"+447890"}}.

@bheklilr esiste un modo per utilizzare la serializzazione di Attach (già definita), invece di definirla di nuovo?

ho provato un po 'di sintassi sciocchezza, ma comprensibilmente non si compila:

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

È possibile scrivere manualmente le istanze 'ToJSON' e' FromJSON' per ottenere esattamente ciò che si desidera: 'toJSON (AttachMsg (Attach tel)) = oggetto [" attach ". = Object [" tel ". = Tel] ] ', e allo stesso modo per' FixMsg'. L'implementazione di 'parseJSON' non sarebbe molto più difficile. – bheklilr

+0

@bheklilr Grazie, è stato davvero utile. Potresti farne una risposta?Potresti vedere la mia domanda aggiornata ... – fadedbee

risposta

5

Usa personalizzati invece di defaultOptions. È possibile ottenere la struttura corretta utilizzando sumEncoding = ObjectWithSingleField, che riduce il primo esempio a {"AttachMsg":{"tel":"+447890"}}. È quindi possibile personalizzare i tag del costruttore utilizzando constructorTagModifier = myConstructorTag e scrivendo una funzione myConstructorTag che personalizza i nomi a proprio piacimento (ad esempio AttachMsg -> attach).

A titolo di esempio, si otterrà l'output desiderato scrivendo questo in un modulo separato, l'importazione e l'utilizzo myOptions invece di defaultOptions:

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

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

È necessario un modulo separato qui a causa di Template Haskell. C'è probabilmente un modo per definire myConstructorTag e myOptions in un modo migliore per soddisfare i bisogni di TH, ma non ho assolutamente idea di come farlo.

+0

Grazie, sembra la risposta giusta: si sta facendo tardi, quindi ci proverò domani. – fadedbee

+0

Funziona perfettamente - il file separato per la definizione delle opzioni non è un problema. – fadedbee

4

È possibile ottenere Aeson di saltare automaticamente i campi nulli. Io di solito faccio in combinazione con l'estensione DeriveGeneric:

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

che ti dà: opzioni

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

Grazie per la tua risposta, ma non mi sono spiegato abbastanza chiaramente. Voglio l'output '{" attach ": {" tel ":" + 447890 "}}' dal tipo di dati originale 'MsgIn'. – fadedbee

+1

Questa è ancora informazioni utili, anche se non risponde alla domanda, quindi lo sto inviando comunque. – Carl

+0

Sì, ho anche svalutato come è utile. – fadedbee

Problemi correlati