Как вложить поле в пользовательский сериализатор структуры? - PullRequest
2 голосов
/ 27 мая 2020

Учитывая следующее определение struct.

#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub (in crate) struct ResponseError {
    pub status: StatusCode,
    pub title: String,
    pub message: String,
    pub trace: Option<String>,
}

Как сгруппировать некоторые поля в пространство имен (вложить их) во время сериализации? Например, сгруппируйте поля title, message и trace в пространство имен error, как показано ниже.

{
    "status": 0,
    "error": {
        "title": "",
        "message": "",
        "trace": "",
    },
}

Цель состоит в том, чтобы иметь возможность создать плоский struct в Rust при выводе структурированного ответа. Это подход, который я придумал.

impl Serialize for ResponseError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("Color", if self.trace.is_some() { 4 } else { 3 })?;
        state.serialize_field("status", &self.status)?;
        state.serialize_field("error.title", &self.title)?;
        state.serialize_field("error.message", &self.message)?;
        if let Some(trace) = self.trace {
            state.serialize_field("error.trace", &self.trace)?;
        };
        state.end()
    }
}

Ответы [ 2 ]

1 голос
/ 27 мая 2020

Один из способов приблизиться к этому - определить отдельные структуры для вашего «формата проводов», затем предоставить реализации From<> для преобразования в форматы проводов и обратно, а затем, наконец, использовать from и into serde для выполните окончательные преобразования.

Реализация структур проводов и реализации From немного утомительна, но проста:

use serde::{Serialize,Deserialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(from = "ResponseErrorWireFormat", into = "ResponseErrorWireFormat")]
pub struct ResponseError {
    pub status: String,
    pub title: String,
    pub message: String,
    pub trace: Option<String>,
}

#[derive(Serialize, Deserialize)]
pub struct ResponseErrorInfoWireFormat {
    pub title: String,
    pub message: String,
    pub trace: Option<String>, 
}

#[derive(Serialize, Deserialize)]
pub struct ResponseErrorWireFormat {
    pub status: String,
    pub info: ResponseErrorInfoWireFormat
}

impl From<ResponseErrorWireFormat> for ResponseError {
    fn from(v: ResponseErrorWireFormat) -> ResponseError { 
        ResponseError {
            status: v.status,
            title: v.info.title,
            message: v.info.message,
            trace: v.info.trace,
        }
    }
}

impl From<ResponseError> for ResponseErrorWireFormat {
    fn from(v: ResponseError) -> ResponseErrorWireFormat { 
        ResponseErrorWireFormat {
            status: v.status,
            info: ResponseErrorInfoWireFormat {
                title: v.title,
                message: v.message,
                trace: v.trace,
            }
        }
    }
}

Тогда код для использования прост:

fn main() {
    let v = ResponseError {
        status: "an error".to_string(),
        title: "an error title".to_string(),
        message: "oh my, an error!".to_string(),
        trace: Some("it happened right here.".to_string()),
    };

    let serialized = serde_json::to_string(&v).unwrap();
    println!("serialized = {}", serialized);
    let deserialized: ResponseError = serde_json::from_str(&serialized).unwrap();
    println!("deserialized = {:?}", deserialized);
}

Полный пример можно найти здесь

0 голосов
/ 27 мая 2020

Это больше похоже на «антитезис» к вашему методу. Вы можете сгруппировать эти ключи в отдельной структуре и использовать serde(flatten) для сглаживания ответа.

#[derive(Serialize, Deserialize)]
struct ErrorInfo {
    pub title: String,
    pub message: String,
    pub trace: Option<String>,
}

#[derive(Serialize, Deserialize)]
struct ErrorResponse {
    pub status: StatusCode,

    #[serde(flatten)]
    error: ErrorInfo,
}

Ознакомьтесь с do c по сглаживанию структуры здесь https://serde.rs/attr-flatten.html.

...