How would you setup the impl functions of FromSql and ToSql for a Postgres Jsonb type, that can be an array of data

Viewed 335

How would you setup the impl functions of FromSql and ToSql for a Postgres Jsonb type, that can be an array of data.

Here's some example array json data.

[
    {
        "title": "some text",
        "date_str": null,
    },
    {
        "title": "some more text",
        "date_str": "Feb 25th",
    },
]

Here's the code I currently have. I was able to figure out the none array implementation without issue, but I'm struggling to create the ToSql and FromSql for the wrapper type (Vec). I get the error "Error(DeserializationError(error("invalid type: map, expaected a sequence", line: 0, column: 0)))", and I'm not sure what change I need to make.

// models.rs
#[derive(Debug, Serialize, Deserialize, Queryable, QueryableByName)]
#[table_name = "table"]
pub struct table {
    pub id: i32,
    pub cust_jsonb: CustJsonbs,
}

#[derive(FromSqlRow, AsExpression, Debug, Serialize, Deserialize)]
#[sql_type = "Jsonb"]
pub struct CustJsonb {
    pub title: String,
    pub date_arg: Option<String>,
}

//wrapper type
#[derive(FromSqlRow, AsExpression, Debug, Serialize, Deserialize)]
#[sql_type = "Jsonb"]
pub struct CustJsonbs (
    Vec<CustJsonb>
);

// None array implementation that works
impl FromSql<Jsonb, Pg> for CustJsonb {
    fn from_sql(bytes: Option<&[u8]>) -> diesel::deserialize::Result<Self> {
        let value = <serde_json::Value as FromSql<Jsonb, Pg>>::from_sql(bytes)?;
        Ok(serde_json::from_value(value)?)
    }
}

impl ToSql<Jsonb, Pg> for CustJsonb {
    fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> diesel::serialize::Result {
        let value = serde_json::to_value(self)?;
        <serde_json::Value as ToSql<Jsonb, Pg>>::to_sql(&value, out)
    }
}

// Array implementation - I've tried (doesn't work and not sure how to fix it exactly)
impl FromSql<Jsonb, Pg> for CustJsonbs {
    fn from_sql(bytes: Option<&[u8]>) -> diesel::deserialize::Result<Self> {
        let value = <serde_json::Value as FromSql<Jsonb, Pg>>::from_sql(bytes)?;
        Ok(serde_json::from_value(value)?)
    }
}

impl ToSql<Jsonb, Pg> for CustJsonbs {
    fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> diesel::serialize::Result {
        let value = serde_json::to_value(self)?;
        <serde_json::Value as ToSql<Jsonb, Pg>>::to_sql(&value, out)
    }
}
1 Answers

So the answer to this is easy.

impl FromSql<Jsonb, Pg> for CustJsonbs {
    fn from_sql(bytes: Option<&[u8]>) -> diesel::deserialize::Result<Self> {
        let value = <serde_json::Value as FromSql<Jsonb, Pg>>::from_sql(bytes)?;
        Ok(serde_json::from_value(value)?)
    }
}

The last line in this function instructs to construct the struct directly from the returned value. That fails because the returned value is an array and not an struct, as the error message indicates.

A fixed version would just construct only the inner array from the json value and manually add the outer new type wrapper.

impl FromSql<Jsonb, Pg> for CustJsonbs {
    fn from_sql(bytes: Option<&[u8]>) -> diesel::deserialize::Result<Self> {
        let value = <serde_json::Value as FromSql<Jsonb, Pg>>::from_sql(bytes)?;
        Ok(Self(serde_json::from_value(value)?)) 
        // ^^^^ note the additional `Self` there
        // Thats required to tell serde that we only want
        // to construct the inner array from the provided json
    }
}
``
Related