Creare un custom deserializer in serde e sqlx

Mattepuffo's logo
Creare un custom deserializer in serde e sqlx

Creare un custom deserializer in serde e sqlx

Lo scenario è questo:

  • web service che usa Axum, sqlx e serde (ovviamente)
  • dal client arriva un JSON in post che ha un campo così --> "ag_utente_fk": "1"
  • ma nella struct è mappato così --> pub ag_utente_fk: i32

In Rust questa cosa non è concessa, cioè non fa conversioni automatiche stile PHP/Javascript.

Abbiamo due soluzioni.

Una è di forzare lato client l'invio di un intero; il problema è che potreste non essere voi gli sviluppatori del frontend, e potreste non sapere neanche da dove vi arriva il dato (cURL, React, Angular, ecc).

La seconda soluzione, quella che sto adattoando, è creare un costum deserializer in cui facciamo la conversione noi.

Vi propongo quindi la mia struct con la funzione custom:

#[derive(Serialize, FromRow, Deserialize)]
pub struct Anagrafica {
    pub ag_id: Option<i32>,
    pub ag_denominazione: Option<String>,
    pub ag_alias: Option<String>,
    pub ag_email: Option<String>,
    pub ag_piva: Option<String>,
    pub ag_cf: Option<String>,
    pub ag_pec: Option<String>,
    pub ag_codice_ident: Option<String>,
    pub ag_telefono: Option<String>,
    pub ag_indirizzo: Option<String>,
    pub ag_cap: Option<String>,
    pub ag_citta: Option<String>,
    pub ag_paese: Option<String>,
    pub ag_web: Option<String>,
    pub ag_logo: Option<String>,
    pub ag_note: Option<String>,
    #[serde(deserialize_with = "deserialize_i32_from_string")]
    pub ag_utente_fk: i32,
    pub ag_provincia: Option<String>,
    pub ag_data_aggiunta: Option<NaiveDateTime>,
    pub ag_data_modifica: Option<NaiveDateTime>,
}

fn deserialize_i32_from_string<'de, D>(deserializer: D) -> Result<i32, D::Error>
where
    D: serde::Deserializer<'de>,
{
    use serde::de::{Error, Unexpected};
    use std::str::FromStr;

    let value = serde_json::Value::deserialize(deserializer)?;
    match value {
        serde_json::Value::Number(n) => n
            .as_i64()
            .map(|v| v as i32)
            .ok_or_else(|| Error::invalid_type(Unexpected::Other("number"), &"integer")),
        serde_json::Value::String(s) => i32::from_str(&s)
            .map_err(|_| Error::invalid_type(Unexpected::Str(&s), &"integer string")),
        _ => Err(Error::invalid_type(
            Unexpected::Other("non-integer"),
            &"integer or string integer",
        )),
    }
}

In questo modo viene accettato sia "1" che 1.

Enjoy!


Condividi

Commentami!