#3097·serde

Serde drops error source information when deserializing using try_from

Author: axokingCreated Aug 16, 2026Updated Sep 6, 2026

When using a custom type and #[serde(try_from = "..."], errors that the try_from call throws are not forwarded correctly. Instead, they are converted into strings with no control about how the conversion behaves, and the original error is dropped instead of saving it as a source. This means that the actual error that caused the deserialization to fail will be forgotten if it is wrapped in some context management.

Example code:

rust
use anyhow::{Context, anyhow};
use serde_derive::Deserialize;

#[derive(Deserialize, Debug)]
#[serde(try_from = "u8", into = "u8")]
enum MyType {
	Alpha, Bravo, Charlie
}

impl TryFrom<u8> for MyType {
	type Error = anyhow::Error;

	fn try_from(value: u8) -> Result<Self, Self::Error> {
		number_to_my_type(value)
	}
}

fn number_to_my_type(number: u8) -> Result<MyType, anyhow::Error> {
	// this is the only error which will be printed
	number_to_my_type_inner(number).context("number to my type: Outer function")
}

fn number_to_my_type_inner(number: u8) -> Result<MyType, anyhow::Error> {
	match number {
		67 => Ok(MyType::Alpha),
		68 => Ok(MyType::Bravo),
		69 => Ok(MyType::Charlie),
		// but I want to see this important error message
		num => Err(anyhow!("IMPORTANT ERROR MESSAGE: {num} was an invalid number"))
	}
}

fn parse_json() -> Result<MyType, anyhow::Error> {
	serde_json::from_str("128")
		// at this point, a serde_json Error is returned, which is created by calling custom() I guess
		// the problem is that custom() takes a variable of type Into<String>, so the only thing serde_json can do with it
		// is to convert it to a string, but it can't save any important information about the error
		.context("Parsing a string")
}

fn main() {
	let x = parse_json().expect("error");
	println!("{x:?}");
}

This code prints

thread 'main' (119114) panicked at src/main.rs:36:26:
error: Parsing a string

Caused by:
    number to my type: Outer function