Serde json - how to create generic function for de-serialization

Hi guys,

I wrote an api struct that I'm using as an interface to backend api. It's all working nice and well, but I have a lot of code duplication, which basically does same thing, but every time for different structs.

Code example:

let obj: Vec<MyObjectStruct> = serde_json::from_str(&response_body).map_err(|err| {
            if status.is_server_error() {
                let api_err: APIError = serde_json::from_str(&response_body)
                    .map_err(|err| APIClientError::DeSerializerError(err.to_string()))
                error!("{}", api_err.detail);
                return APIClientError::HTTPRequestError(api_err.detail);
            error!("{}", err.to_string());
            APIClientError::DeSerializerError("Failed to parse error response".to_string())

Is there anyway to have this in a function that takes an object to deserialize to and then to do the same logic?

As a followup, I am also wondering if there is any way to map over a number of error structs and attempt to deserialize to them one by one?

Like this?

fn deserialize_body<'de, T>(body: &'de [u8], status: StatusCode) -> Result<T, ApiClientError>
    T: Deserialize<'de>
    serde_json::from_slice(body).map_err(|err| {
            if status.is_server_error() {
                let api_err: Result<ApiError, _> = serde_json::from_slice(body);
                match api_err {
                    Ok(api_err) => {
                        // error!("{}", api_err.detail);
                    Err(err) => ApiClientError::DeserializerError(err)
            } else {
                // error!("{}", err.to_string());


yes!! :smile:

What does it mean 'de? i know that serde_json has de module.
I don't fully understand how this works :sob:
And another question, is from_slice preferred over from_str?

Basically if you have the json string { "value": "test" }, you can avoid copying the two strings into your value: Instead you can simply use a slice into the original json data. Of course this introduces the difficulty that your deserialized value now borrows from the body, as it contains slices into the body. The use of 'de allows (but does not require) the type T to borrow from the body in this manner.

Notice first that the body has type &'de [u8]. This means that the body is alive for some lifetime we will denote by 'de.

Notice next that we have the constraint T: Deserialize<'de>. This means that the type T can be deserialized from byte slices alive for at least the lifetime 'de, and values of type T are allowed to borrow from this byte slice.

So basically the use of this lifetime means that the returned type T is allowed (but not required) to borrow data from the provided byte slice. You could also do this

fn deserialize_body<T>(body: &[u8], status: StatusCode) -> Result<T, ApiClientError>
    T: DeserializeOwned

This would mean that T is not allowed to borrow from the body, i.e. it must itself own the associated values. The DeserializeOwned version above is less powerful than my original solution.

As for the de module, it's just short for deserialize. The lifetime 'de is just a name, and I could have called it 'a instead if I wanted, but the convention is to call it 'de when working with serde.

You can read more about this in the serde guide.

If you put your data into a string before parsing it, the raw body must be valid utf-8 data, and your code will panic if this is not the case. By using a byte slice, this issue is avoided, and if parts of the body is deserialized into a string, only that part will be utf-8 checked, and it wont happen twice.

1 Like

Thank you again. Now it makes more sense to me. :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.