2016-07-05 10 views
2

sein kann Ich arbeite mit einigen komplex formatierten JSON-Antworten von einem REST-Server. Um sie zu dekodieren, habe ich ein paar Datentypen, um die verschiedenen verschachtelten Objekte zu behandeln. Zum Beispiel:Aeson dekodieren JSON-Objekt, das eine Zeichenfolge oder ein Int

... Other types ... 

data Profile = 
    Profile { fields :: [KVPair] 
    } deriving (Show) 

instance FromJSON Profile where 
    parseJSON (Object v) = 
    Profile <$> v .: "Fields" 
    parseJSON _ = mzero 

data KVPair = 
    KVPair { key :: Int 
     , value :: String 
    } deriving (Show) 

instance FromJSON KVPair where 
    parseJSON (Object v) = 
    KVPair <$> v .: "Key" 
      <*> v .: "Value" 
    parseJSON _ = mzero 

Alles funktioniert bis auf den letzten KVPair-Typ. Meine JSON-Objekte haben alle Integer-Schlüssel. jedoch können die Werte entweder eine ganze Zahl oder eine Zeichenkette:

 { 
     "Key": 0, 
     "Value": "String Value!" 
     }, 
     { 
     "Key": 1, 
     "Value": 42 
     } 

Jetzt nehme ich an, ich könnte eine andere Summe Typ meinen Wert dekodieren hinzufügen, die von String und Int zusammengesetzt ist, aber ich würde eine ganz zu vermeiden, bevorzugen die Zugabe neuer Typ nur dafür. Hat Aeson eine einfache Möglichkeit, mit diesem Szenario umzugehen?

Antwort

4

Es gibt zwei einfache Fixes. Eine ist einfach zu schreiben

data KVPair = KVPair { key :: Int, value :: Value } 

und lassen Sie alle anderen Code gleich. Verbraucher müssen die Value überprüfen, um zu sehen, ob es ein String-y-Ding oder ein Nummer-y-Ding ist.

Wahrscheinlich ist der bessere Weg, einfach zwei alternative Parser bereitzustellen, die beide in Ihr gewünschtes Format konvertieren. Zum Beispiel Ihrer KVPair Definition zu halten wie es ist, könnte man schreiben

showInt :: Int -> String 
showInt = show 

instance FromJSON KVPair where 
    parseJSON (Object v) 
     = KVPair 
     <$> v .: "Key" 
     <*> (v .: "Value" <|> (showInt <$> v .: "Value")) 

Das Beste aus beiden Welten die Informationen über zu halten wäre, ob es eine String oder Int um und auf andere Arten von Werten ablehnen; z.B.

data KVPair = KVPair { key :: Int, value :: Either String Int } 

instance FromJSON KVPair where 
    parseJSON (Object v) 
     = KVPair 
     <$> v .: "Key" 
     <*> ( (Left <$> v .: "Value") 
      <|> (Right <$> v .: "Value") 
      ) 
+0

Wenn ich Ihre alternative Analyse Methode richtig verstehe, würde es nur die ganzzahligen Werte zu einem String-Typ, richtig? Also "Value": 3 würde zu etwas wie KVPair {value = "3"} oder? – jkeuhlen

+2

@jkeuhlen Genau richtig, obwohl "coerce" in Haskell eine spezifische Bedeutung hat, die hier nicht gilt - "convert" oder "pretty-print" wäre näher zu korrigieren. –

1

Sie müssen nur den Aeson-Werttyp verwenden, um mit einem Objekt mit Feldern zu arbeiten, die einen beliebigen JSON-Wert haben können.

+0

Ein Objekt ist bereits eine HashMap (in Aeson). Suchen Sie einfach den Wert, den Sie brauchen. Wenn das nicht genug ist, könnten Sie den JSON in eine Entweder Zeichenfolge int analysieren – dysinger