2017-06-02 4 views
6

Ich möchte Serde verwenden, um einige JSON als Teil einer HTTP-PATCH-Anfrage zu analysieren. Da PATCH-Anforderungen nicht das gesamte Objekt übergeben, sondern nur die relevanten zu aktualisierenden Daten, muss ich zwischen einem Wert, der nicht übergeben wurde, einem Wert, der explizit auf null gesetzt wurde, und einem Wert, der vorhanden ist, unterscheiden.Wie kann ich unterscheiden zwischen einem deserialisierten Feld, das fehlt, und einem, das null ist?

Ich habe einen Wert Objekt mit mehreren Nullable-Felder:

struct Resource { 
    a: Option<i32>, 
    b: Option<i32>, 
    c: Option<i32>, 
} 

Wenn der Kunde diese JSON wie einreicht:

{"a": 42, "b": null} 

Ich möchte a-Some(42), b-None ändern, und lassen Sie c unverändert.

Ich habe versucht, jedes Feld in einer weiteren Ebene von Option Verpackung:

#[derive(Debug, Deserialize)] 
struct ResourcePatch { 
    a: Option<Option<i32>>, 
    b: Option<Option<i32>>, 
    c: Option<Option<i32>>, 
} 

Aber das unterscheidet nicht zwischen b und c machen; beide sind None aber ich hätte b gewünscht, Some(None) zu sein.

Ich bin nicht an diese Darstellung verschachtelt gebunden Option s; Jede Lösung, die die 3 Fälle unterscheiden kann, wäre in Ordnung, wie eine, die eine benutzerdefinierte Aufzählung verwendet.

Antwort

5

Sehr wahrscheinlich ist der einzige Weg, um das jetzt zu erreichen, mit einer benutzerdefinierten Deserialisierungsfunktion. Glücklicherweise ist es nicht schwer zu implementieren, auch um es für jede Art von Feld funktioniert:

fn deserialize_optional_field<'de, T, D>(deserializer: D) 
    -> Result<Option<Option<T>>, D::Error> 
    where D: Deserializer<'de>, 
      T: Deserialize<'de> 
{ 
    Ok(Some(Option::deserialize(deserializer)?)) 
} 

Dann würde jedes Feld kommentierte wie so:

#[serde(deserialize_with = "deserialize_optional_field"] 
a: Option<Option<i32>>, 

Sie auch die Struktur zu annotieren müssen mit #[serde(default)], so dass leere Felder zu einem "unverpackten"None deserialisiert werden. Der Trick besteht darin, die aktuellen Werte um Some zu wickeln.

Serialisierung setzt auf einen anderen Trick: Serialisierung Überspringen wenn das Feld None:

#[serde(deserialize_with = "deserialize_optional_field"] 
#[serde(skip_serializing_if = "Option::is_none")] 
a: Option<Option<i32>>, 

Playground mit dem vollständigen Beispiel. Der Ausgang:

Original JSON: {"a": 42, "b": null} 
> Resource { a: Some(Some(42)), b: Some(None), c: None } 
< {"a":42,"b":null} 
3

Gebäude weg von E_net4's answer, können Sie auch eine Enumeration für die drei Möglichkeiten schaffen:

#[derive(Debug)] 
enum Patch<T> { 
    Missing, 
    Null, 
    Value(T), 
} 

impl<T> Default for Patch<T> { 
    fn default() -> Self { 
     Patch::Missing 
    } 
} 

impl<T> From<Option<T>> for Patch<T> { 
    fn from(opt: Option<T>) -> Patch<T> { 
     match opt { 
      Some(v) => Patch::Value(v), 
      None => Patch::Null, 
     } 
    } 
} 

impl<'de, T> Deserialize<'de> for Patch<T> 
where 
    T: Deserialize<'de>, 
{ 
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 
    where 
     D: Deserializer<'de>, 
    { 
     Option::deserialize(deserializer).map(Into::into) 
    } 
} 

Dies kann dann verwendet werden, wie:

Leider
#[derive(Debug, Deserialize)] 
struct ResourcePatch { 
    #[serde(default)] 
    a: Patch<i32>, 
} 

, Sie müssen immer noch jedes Feld mit #[serde(default)] annotieren (oder es auf die gesamte Struktur anwenden). Im Idealfall würde die Implementierung von Deserialize für Patch das vollständig behandeln, aber ich habe noch nicht herausgefunden, wie man das noch macht.

+1

"Idealerweise würde die Implementierung von' Deserialize' für 'Patch' das komplett behandeln." Meine Annahme ist bisher, dass dies unmöglich ist. Von dem Moment an, in dem Sie versuchen, einen 'Patch' zu deserialisieren, erwarten Sie, dass der Wert in seiner serialisierten Form existiert. Im Gegensatz dazu existiert "Patch :: Missing" durch seine "Nichtexistenz" in unserem Container (Sie können "Patch :: Missing" nicht selbstständig zu JSON serialisieren). AFAIK a 'Serialize' kann nicht wählen, nicht serialisiert zu werden, noch kann es dem Container mitteilen, diesen Teil des Prozesses zu überspringen. –

+0

Nun, eigentlich ist mein vorheriger Kommentar eher auf die Serialisierung und nicht auf die Deserialisierung anwendbar. Unabhängig davon ist die Logik etwas dual: 'Deserialize' kann dem' Deserializer' nicht erlauben, Werte zu deserialisieren, die er nicht finden kann. Wenn wir ein leeres Objekt '{}' haben, können die Implementierungen von 'Deserialize' nichts dagegen tun, aber der Deserializer kann, sobald er weiß, dass er einen Standard ausfüllt. –

Verwandte Themen