英文:
How customize serialization of a result-type using serde?
问题
#[derive(Serialize, Deserialize)]
struct ServerResponse<D, E> {
result: Result<D, E>,
version: String,
// More parameters here
}
我想要能够将这个结构序列化和反序列化为以下格式。
如果结果成功
{
"result": "my_result_data",
"version": "1.0"
}
如果结果不成功
{
"error": { "message": "请求失败", "error-code": 212 },
"version": "1.0"
}
英文:
I have a struct which contains a Result field that I want to serialize
#[derive(Serialize, Deserialize)]
struct ServerResponse<D,E> {
result : Result<D,E>,
version : String,
// More parameters here
}
I want to be able to serialize and deserialize this struct to the following-format.
If result is successful
{
"result" : "my_result_data",
"version" : "1.0",
}
If the result is not successful
{
"error" : { "message" : "The request failed", "error-code" : 212 },
"version" : "1.0"
}
答案1
得分: 1
我对你的问题有一个疑问:你希望从一个包含 message
和 error-code
字段的 JSON 对象中反序列化 Err
,然而它是一个泛型参数,并且没有指示它受到任何特定类型的约束。
我选择通过定义一个新的 trait 来解决这个问题:
pub trait ResponseError {
fn message(&self) -> &str;
fn error_code(&self) -> i32;
fn from_parts(message: &str, code: i32) -> Self;
}
任何想要被序列化的 ServerResponse
必须满足 E: ResponseError
。这可能完全是错误的做法,但根据我所了解的有限信息,这是我能做的最好的事情。
实际的实现相当机械:
- 设置
ServerResponse
委托序列化和反序列化给一组自定义函数:
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound(
serialize = "D: Serialize, E: ResponseError",
deserialize = "D: DeserializeOwned, E: ResponseError"
))]
struct ServerResponse<D, E> {
#[serde(with = "untagged_ok_result")]
result: Result<D, E>,
version: String,
// 更多参数在这里
}
- 搭建这些函数的框架:
mod untagged_ok_result {
pub fn serialize<'a, S, T, E>(v: &Result<T, E>, ser: S) -> Result<S::Ok, S::Error> { todo!() }
pub fn deserialize<'de, D, T, E>(de: D) -> Result<Result<T, E>, D::Error> { todo!() }
}
- 定义一个 Rust 类型,其中派生的 De/Serialize 匹配实际的 JSON 格式:
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
#[serde(bound(deserialize = "D: Deserialize<'a>"))]
enum Mresult<'a, D> {
Ok(D),
Err {
#[serde(borrow)]
message: Cow<'a, str>,
#[serde(rename = "error-code")]
error_code: i32,
},
}
- 通过在
std
的Result
和自定义类型之间进行转换来实现序列化和反序列化:
pub fn serialize<'a, S, T, E>(v: &Result<T, E>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
E: ResponseError,
{
match v {
Ok(v) => Mresult::Ok(v),
Err(v) => Mresult::Err {
message: v.message().into(),
error_code: v.error_code(),
},
}
.serialize(ser)
}
pub fn deserialize<'de, D, T, E>(de: D) -> Result<Result<T, E>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
E: ResponseError,
{
Ok(match Mresult::deserialize(de)? {
Mresult::Ok(v) => Ok(v),
Mresult::Err {
message,
error_code,
} => Err(ResponseError::from_parts(&message, error_code)),
})
}
英文:
I have one issue with your question: You want Err
to be deserialized from a json object with the fields message
and error-code
, yet it's a generic parameter and there's no indication that it is constrained to any specific type.
I've chosen to work around that issue by defining a new trait:
pub trait ResponseError {
fn message(&self) -> &str;
fn error_code(&self) -> i32;
fn from_parts(message: &str, code: i32) -> Self;
}
Any ServerResponse
that wants to be serialized must E: ResponseError
. That might entirely be the wrong thing to do, but with what limited information I have, it's the best thing I can do.
The actual implementation is quite mechanic:
-
Set up
ServerResponse
to delegate de/serialization to a set of custom functions:#[derive(Serialize, Deserialize, Debug)] #[serde(bound( serialize = "D: Serialize, E: ResponseError", deserialize = "D: DeserializeOwned, E: ResponseError" ))] struct ServerResponse<D, E> { #[serde(with = "untagged_ok_result")] result: Result<D, E>, version: String, // More parameters here }
-
Scaffold said functions
mod untagged_ok_result { pub fn serialize<'a, S, T, E>(v: &Result<T, E>, ser: S) -> Result<S::Ok, S::Error> { todo!() } pub fn deserialize<'de, D, T, E>(de: D) -> Result<Result<T, E>, D::Error> { todo!() } }
-
Define a Rust type where derived De/Serialize matches whatever actual JSON format you want
#[derive(Serialize, Deserialize)] #[serde(untagged)] #[serde(bound(deserialize = "D: Deserialize<'a>"))] enum Mresult<'a, D> { Ok(D), Err { #[serde(borrow)] message: Cow<'a, str>, #[serde(rename = "error-code")] error_code: i32, }, }
-
Implement de/serialization by converting between the
std
Result
and the custom type:pub fn serialize<'a, S, T, E>(v: &Result<T, E>, ser: S) -> Result<S::Ok, S::Error> where S: Serializer, T: Serialize, E: ResponseError, { match v { Ok(v) => Mresult::Ok(v), Err(v) => Mresult::Err { message: v.message().into(), error_code: v.error_code(), }, } .serialize(ser) } pub fn deserialize<'de, D, T, E>(de: D) -> Result<Result<T, E>, D::Error> where D: Deserializer<'de>, T: Deserialize<'de>, E: ResponseError, { Ok(match Mresult::deserialize(de)? { Mresult::Ok(v) => Ok(v), Mresult::Err { message, error_code, } => Err(ResponseError::from_parts(&message, error_code)), }) }
(I was tempted to use the from
/into
attributes for a while, but those are container attributes, which causes other boilerplate. It might be an option for you anyway)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论