Deserializing JSON to struct, how can I refactor into a pipeline?

I have the following code that reads the contents of a json file and deserializes the string into a vector of Todos:

fn deserialize_todos(filepath: &Path) -> Result<Vec<Todo>, &str> {
    let string = match std::fs::read_to_string(filepath) {
        Ok(string) => string,
        Err(_) => return Err("Could not read file"),
    };

    match serde_json::from_str::<Vec<Todo>>(&string) {
        Ok(todos) => Ok(todos),
        Err(_) => Err("Could not parse JSON"),
    }
}

I have two issues with this:

  1. I'm pretty sure there's a way to make the logic more similar to a pipeline, something like the following pseudo-code
fn deserialize_todos(filepath: &Path) -> Result<Vec<Todo>, &str> {
    Ok(filepath)
         .??(std::fs::read_to_string, "Could not read file")
         .??(serde_json::from_str::<Vec<TodoItem>>, "Could not parse JSON")
}

for some ?? method on Result which I'm not aware of.

  1. Two things could fail: either the file cannot be read or it cannot be parsed into JSON. I'd like to return the relative errors, i.e. std::io::Error if std::fs::read_to_string fails or serde_json::Error if serde_json::from_str fails. Instead I'm returning a string slice because I'm not sure how to express either Err(a) or Err(b) as the error type of the function. How does Rust deal with multiple possible error types?

How does Rust deal with multiple possible error types?

You define an enum with a variant for each error type that can occur, e.g:

enum DeserializeError {
    Io(std::io::Error),
    Json(serde_json::Error),
}

I'm pretty sure there's a way to make the logic more similar to a pipeline

Your code could be rewritten as:

fn deserialize_todos(filepath: &Path) -> Result<Vec<Todo>, DeserializeError> {
    let string = std::fs::read_to_string(filepath).map_err(DeserializeError::Io)?;
    Ok(serde_json::from_str::<Vec<Todo>>(&string).map_err(DeserializeError::Json)?)
}

See the question mark operator.

Note that if you e.g. implement From<std::io::Error> for DeserializeError you can omit the map_err. Such from impls can also be generated using thiserror.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.