2015-07-15 4 views
5

Ich habe einige Zeit damit verbracht, mit Aeson herumzuspielen, aber ich kann Algebraische Datentypen nicht serialisieren.Wie man Haskell ADTs als ordentliches JSON mit Aeson serialisiert?

Was ich versucht habe, ist:

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) 

Die Ausgabe lautet:

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

Der Ausgang ich suche ist:

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

aber vom Typ MsgIn , anstatt MsgIn2.

(Die Ausgabe von MsgIn2 recht nahe kommt, aber es ist eine explizites null bekommt.)

Gibt es eine Möglichkeit, dies in Aeson zu tun?


Update:

ich hinzugefügt:

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

und bekam die Antwort, die ich wollte: {"attach":{"tel":"+447890"}}.

@bheklilr Gibt es eine Möglichkeit, die (bereits definierte) Serialisierung von Attach zu verwenden, anstatt sie erneut zu definieren?

Ich habe einige Unsinn Syntax versucht, aber verständlicherweise es nicht kompilieren:

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

Sie könnten einfach die 'ToJSON'- und' FromJSON'-Instanzen manuell schreiben, um genau das zu bekommen, was Sie wollen: 'toJSON (AttachMsg (Attach tel)) = Objekt [" attach ". = Objekt [" tel ". = Tel] ] ', und ähnlich für' FixMsg'. Die 'parseJSON'-Implementierung wäre nicht viel schwieriger. – bheklilr

+0

@bheklilr Danke, das war wirklich nützlich. Könnten Sie es zu einer Antwort machen?Könnten Sie meine aktualisierte Frage sehen? – fadedbee

Antwort

5

Verwenden Sie benutzerdefinierte Optionen statt defaultOptions. Sie können die richtige Struktur mit sumEncoding = ObjectWithSingleField erhalten, die Ihr erstes Beispiel auf {"AttachMsg":{"tel":"+447890"}} reduziert. Sie können die Konstruktortags dann anpassen, indem Sie constructorTagModifier = myConstructorTag verwenden und eine Funktion myConstructorTag schreiben, die die Namen nach Ihren Wünschen anpasst (z. B. AttachMsg -> attach).

Als Beispiel sehen Sie die Ausgabe, die Sie dies durch das Schreiben in ein separates Modul wollen bekommen, es zu importieren und mit myOptions statt defaultOptions:

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

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

Ein separates Modul wegen Vorlage benötigt wird hier Haskell. Es gibt wahrscheinlich eine Möglichkeit, myConstructorTag und myOptions besser zu definieren, um die Anforderungen von TH zu erfüllen, aber ich habe absolut keine Ahnung, wie das geht.

+0

Danke, das sieht nach der richtigen Antwort aus - es wird spät, also werde ich es morgen versuchen. – fadedbee

+0

Funktioniert perfekt - separate Datei zum Definieren von Optionen ist kein Problem. – fadedbee

4

Sie erhalten Aeson können Null-Felder automatisch zu überspringen. Ich tue dies in der Regel in Kombination mit der DeriveGeneric Erweiterung:

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

, die gibt Ihnen:

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

Danke für Ihre Antwort, aber ich habe mich nicht klar genug erklärt. Ich möchte die Ausgabe '{" attach ": {" tel ":" + 447890 "}}' aus dem ursprünglichen 'MsgIn'-Datentyp. – fadedbee

+1

Dies ist immer noch nützliche Info, auch wenn es die Frage nicht beantwortet, also werde ich es trotzdem upvotieren. – Carl

+0

Ja, ich habe auch Upvoted, wie es nützlich ist. – fadedbee